Implicit 核發流程


假設啦!因為 gossip 微網誌很受歡迎,有人打算用 JavaScript、HTML5 之類的前端技術,幫 gossip 微網誌的使用者寫個在「瀏覽器中執行的應用程式」,協助使用者管理訊息、做做文字雲分析之類,不過 gossip 微網誌是我在管理的,我不能私下直接給你使用者名稱、密碼,使用者也不會給你這些敏感資訊,怎麼辦呢?

如果我支援 OAuth 2 的 Implicit 核發流程,就可以解決這件事,要建立這麼一個授權伺服器,基本流程與〈Password Credentials 核發流程〉類似,最主要在於授權類型等需要做些變更,因此,若以〈Password Credentials 核發流程〉中的 AuthSvr 成果來修改:

package cc.openhome;

...

@SpringBootApplication
@EnableAuthorizationServer
public class AuthSvrApplication {
    ...略

    @Bean
    public AuthorizationServerConfigurer authorizationServerConfigurer() {
        return new AuthorizationServerConfigurerAdapter() {
            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.inMemory()
                       .withClient("browserclient")
                       .secret(passwordEncoder.encode("browserclient12345678"))
                       .scopes("account", "message", "email")
                       .resourceIds("resource")
                       .authorizedGrantTypes("implicit")
                       .redirectUris("http://localhost:8082/hello.html");
            }

            ...略        
        };
    }

    ...略
}

在這邊可以看到 authorizedGrantTypes 設為 "implicit",並且指定了個 redirectUris,這是待會請求核發 Access Token 時必須指定的重導網址,瀏覽器收到 Access Token 後也確實會重導至該網址,實際上,對於第三方應用程式,我必須有個地方可以讓它們申請建立應用程式,建立應用程式時會需要填寫重導網址、應用程式會使用到的 scope 資訊,如果你曾經在社交網站上開設過應用程式,對這個動作應不陌生,在這邊為了簡化範例,就直接寫死在程式碼中。

接下來啟動授權伺服器,打開瀏覽器請求 http://localhost:8081/oauth/authorize?response_type=token&client_id=browserclient&redirect-uri=http://localhost:8082/hello.html,注意到端點是 /oauth/authorize,請求參數 response_typetoken,表示請求核發 Access Token,client_id 為客戶端 ID,redirect-uri 為收到 Access Token 時要重導的網址,必須與方才的 redirectUris 設定相同。

因為第三方應用程式未持有使用者名稱、密碼,實際上會是使用者連到第三方應用程式,而被要求重新導向至 http://localhost:8081/oauth/authorize?response_type=token&client_id=browserclient&redirect-uri=http://localhost:8082/hello.html,這時驗證伺服器會要求使用者自身輸入名稱、密碼:

Implicit 核發流程

輸入 caterpillar12345678 之後,會顯示底下頁面,由使用者確認是否允許第三方應用程式拿到這些 scope 的權限:

Implicit 核發流程

喜歡在社交網站上玩第三方應用程式的人,應該很熟悉這個畫面,當然,就開發者而言,這個頁面也是可以自訂的,這我就不談了。

選擇允許的權限範圍後,按下「Authorize」,會被要求重新導向至 http://localhost:8082/hello.html#access_token=236584f9-749e-45db-8489-e9c5066c54a6&token_type=bearer&expires_in=43199&scope=message%20account%20email,注意 # 之後附上了 access_tokentoken_typeexpires_inscope 資訊。

只要有 access_token,就可以對驗證伺服器的 /oauth/check_access 端點進行請求,當然,這個動作是第三方應用程式要做的,你的 hello.html 放在 http://localhost:8082/ 網站上,在重新導向至該站時,會取得 hello.html,然而,瀏覽器不會發送 # 之後的東西,因此你的 hello.html 中必須有 JavaScript 來提取出 access_token。例如:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>

<span id="console"></span>

<script>
    const href = window.location.href;
    const frag = href.split('#')[1];
    const params = frag.split('&');
    const token = params.map(param => param.split('='))
          .reduce((acc, pair) => {
              acc[pair[0]] = pair[1];
              return acc;
          }, {});

    fetch('http://localhost:8080/hello', {
        headers: {          
            'Authorization': 'Bearer ' + token.access_token
        }
    })
    .then(response => response.text())
    .then(text => {
        document.getElementById('console').innerHTML = text;
    });
</script>

</body>
</html>

這是個純 HTML 加 JavaScript,這也是 Implicit 的應用場景,也就是一開頭就談到的,你寫的是「瀏覽器中執行的應用程式」,你大可以把這類應用程式,放在不支援後端技術的內容傳遞網站上。

在上頭的 JavaScript 中,取得了 URI,並從中剖析出 access_token,然後透過 Fetch API 跨站請求了資源伺服器,請求時記得以 Authorization 附上 Access Token,Bearer 表明了 Access Token 的種類。

這種情況下,第三方應用程式得運用跨站請求,因此資源伺服器上的 hello 端點,必須能接受跨站請求,這在 Spring 中只需要標註 @CrossOrigin

@CrossOrigin
@GetMapping("/hello")
public String hello(OAuth2Authentication oauth) {
    return "hello " + oauth.getPrincipal();
}

如果以上設定都沒有錯誤,實際上先前在「Authorize」按下後,你就會看到以下的畫面:

Implicit 核發流程

你可以在 OAuth2ImplicitGrant 找到以上的範例專案。