假設啦!因為 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_type
為 token
,表示請求核發 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
,這時驗證伺服器會要求使用者自身輸入名稱、密碼:
輸入 caterpillar
與 12345678
之後,會顯示底下頁面,由使用者確認是否允許第三方應用程式拿到這些 scope 的權限:
喜歡在社交網站上玩第三方應用程式的人,應該很熟悉這個畫面,當然,就開發者而言,這個頁面也是可以自訂的,這我就不談了。
選擇允許的權限範圍後,按下「Authorize」,會被要求重新導向至 http://localhost:8082/hello.html#access_token=236584f9-749e-45db-8489-e9c5066c54a6&token_type=bearer&expires_in=43199&scope=message%20account%20email
,注意 #
之後附上了 access_token
、token_type
、expires_in
與 scope
資訊。
只要有 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」按下後,你就會看到以下的畫面:
你可以在 OAuth2ImplicitGrant 找到以上的範例專案。