Client Credentials 核發流程(二)


在〈Client Credentials 核發流程(一)〉中,已經實作了授權伺服器,現在來看看客戶端怎麼請求核發 Access Token,這邊使用 Postman 作為客戶端。

請求核發 Access Token 時,客戶端以 POST 指定 grant_typescope,若要求多個 scope,使用空白作區隔(Postman 會自行處理為 +,其他客戶端的話,記得自行處理):

Client Credentials 核發流程(二)

請求的 scope 必須是授權伺服器上有設定的 scope,否則會有錯誤。當然,只有這些資訊的話,授權伺服器並不知道是哪個客戶端在請求核發 Access Token,你必須以 BASIC 驗證方式,告知授權伺服器客戶端名稱與密鑰:

Client Credentials 核發流程(二)

在傳送請求之後,回應中可以看到核發的 Access Token、類型、有效期(預設 12 個小時)與授權範圍:

{
    "access_token": "3023124c-90d1-45fb-9dae-86f2ed1abb6e",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "account email message"
}

在這邊看到 Access Token 使用的是 Bearer,OAuth 2 本身並沒有規範 Access Token 應該是什麼樣子,Bearer 簡單,任何客戶端只要出示 Token,就可以通行於接受 Token 的服務,因此使用時必須記得在加密連線中傳遞,以免被竊取,Bearer 沒有簽署,因而有被修改的風險。

為了避免安全上的問題,以及增加一些 Token 本身攜帶資訊的能力,可以使用 JSON Web Tokens(JWT),它對 Token 制定了規範,具有對 Token 簽署,資源伺服器可以直接確認 Token 等優點,不過需要額外的一些設定,這之後再來談。

客戶端接下來要提取 Access Token,附在 Authorization 請求標頭中來請求資源伺服器,資源伺服器會從標頭中取得 Access Token,並向授權伺服器進行核對,取得授權相關訊息,在〈Client Credentials 核發流程(一)〉看過,我們開放了核對端點 oauth/check_token,請求這個端點時,必須以 BASIC 驗證方式,告知是哪個客戶端 ID 要進行核對,端點接受 token 請求參數,因此如下進行 http://localhost:8081/oauth/check_token?token=3023124c-90d1-45fb-9dae-86f2ed1abb6e 請求就會看到:

Client Credentials 核發流程(二)

回應的訊息中可以看到資源 ID、授權範圍、是否有效、有效期、授權的客戶端 ID 等資訊,資源伺服器可以從回應中取得相關訊息用於安全控管。

來寫個簡單的資源伺服器,同樣地可以選擇 Cloud Auth2 與 Web 的 Starter,build.gradle 中包含:

implementation('javax.xml.bind:jaxb-api:2.2.11') 
implementation('com.sun.xml.bind:jaxb-core:2.2.11')     
implementation('com.sun.xml.bind:jaxb-impl:2.2.11') 
implementation('javax.activation:activation:1.1.1')  

implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.cloud:spring-cloud-starter-oauth2')

接著在 application.properties 中設定核對端點,以及客戶端 ID、密鑰等:

security.oauth2.client.clientId: webclient
security.oauth2.client.clientSecret: webclient12345678
security.oauth2.resource.token-info-uri: http://localhost:8081/oauth/check_token

(實際上,資源伺服器如何與授權伺服器互動,並不在 OAuth 2 的規範中,這只是目前採用 Bearer Token 的情況下,Spring 採用的方式,如果使用 JWT,資源伺服器可以直接確認 Token,就不需要以上的設定。)

接著在啟動主類別上,標註 @EnableResourceServer 表示啟用資源伺服器功能,並設定安全相關資訊:

package cc.openhome;

...

@SpringBootApplication
@EnableResourceServer
@RestController
public class ResSvrApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResSvrApplication.class, args);
    }

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

    @Bean
    public ResourceServerConfigurer resourceServerConfigurer() {
        return new ResourceServerConfigurer() {
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests().antMatchers("/hello").access("#oauth2.hasAnyScope('account', 'message', 'email')");
            }

            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId("resource");
            }
        };
    }
}

留意這邊使用的是 ResourceServerConfigurer,而不是 WebSecurityConfigurer,在設定中,就類似角色設定,使用 #oauth2.hasAnyScope 限制了 /hello 端點,必須被授權了 accountmessageemail 任一範圍的客戶端,才可以進行請求,另外,也設定了資源 ID,客戶端請求的授權資訊中,必須具有相同的資源 ID,才可以請求這邊的資源。

在上頭也寫了個簡單的處理器方法,透過 OAuth2Authentication,可以取得授權的相關資訊,在這邊簡單地取得了客戶端名稱。

來看看 Postman 如何請求資源伺服器,請求時必須於 Authorization 標頭中附上 Access Token:

Client Credentials 核發流程(二)

你可以在 OAuth2ClientGrant 找到授權伺服器與資源伺服器的範例專案。