Authorization Code 核發流程


在〈Implicit 核發流程〉中可以看到,Access Token 就這麼直接地出現在網址列上,雖然 OAuth 2 規範要求核發流程必須在加密連線中進行,不過,如果你對於 Access Token 明明白白地出現在網址列上總有疑慮,那就試著進一步採用 Authorization Code 核發流程。

當客戶端(通常是瀏覽器)請求授權伺服器時,Authorization Code 核發流程會先核發授權碼(Authorization Code)(而不是 Access Token),並要求客戶端重新導向至事先登記好的第三方應用程式網站,授權碼會附在重新導向網址上,第三方應用程式網站要接受授權碼,在後端使用授權碼請求授權伺服器,授權伺服器確認授權碼無誤,才會核發 Access Token 給第三方應用程式網站,整個過程中,客戶端不會知道 Access Token,從而避免 Access Token 曝露在客戶端的問題。

若想實作 Authorization Code 核發流程中的授權伺服器,可以基於〈Implicit 核發流程〉中的成果來修改:

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("authcodeclient")
                       .secret(passwordEncoder.encode("authcodeclient12345678"))
                       .scopes("account", "message", "email")
                       .resourceIds("resource")
                       .authorizedGrantTypes("authorization_code", "refresh_token")
                       .redirectUris("http://localhost:8082/HELLO");
            }

            ...略
    }

    ...略
}

基本上只是在 authorizedGrantTypes 設定 authorization_code,這個模式下支援 Refresh Token,因此也加上了 refresh_token,在重新導向的 URI 設定中,http://localhost:8082/HELLO 將接受授權碼。

啟動授權伺服器之後,可以請求 http://localhost:8081/oauth/authorize?response_type=code&client_id=authcodeclient&redirect-uri=http://localhost:8082/HELLO,注意附上了 response_type=code,表示要求授權碼,也附上了 client_id=authcodeclient,以及 redirect-uri=http://localhost:8082/HELLO,這必須與授權伺服器中的設定相同。

同樣地,如果使用者未登入,授權伺服器會要求使用者登入,接著核取可授權的範圍,在按下「Authorize」之後,授權伺服器會要求重新導向的網址會是 http://localhost:8082/HELLO?code=61vXSV,其中 61vXSV 就是核發的授權碼。

接受授權碼的第三方應用程式網站,拿到授權碼後使用 POST 請求 http://localhost:8081/oauth/token,並在請求本體附上 grant_typecoderedirect_uri,記得要以 Basic 驗證附上客戶端 ID 與密鑰,這樣就可以拿到 Access Token 等資訊:

Authorization Code 核發流程

接著第三方應用程式網站再拿著 Access Token 請求受保護資源,底下的範例,將收到授權碼到請求資源伺服器的流程簡單地實作了一下:

package cc.openhome;

...

@SpringBootApplication
@RestController
public class AppSvrApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppSvrApplication.class, args);
    }

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("HELLO")
    public String app(@RequestParam("code") String code) {
        String accessToken = accessToken(code);

        RequestEntity<Void> request = 
                RequestEntity.get(URI.create("http://localhost:8080/hello"))
                             .header(HttpHeaders.AUTHORIZATION, baerer(accessToken))
                             .build();

        return restTemplate.exchange(request, String.class).getBody().toUpperCase();
    }

    private String baerer(String accessToken) {
        return "Bearer " + accessToken;
    }

    private String accessToken(String code) {
        MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
        map.add("code", code);
        map.add("grant_type", "authorization_code");
        map.add("redirect_uri", "http://localhost:8082/HELLO");

        RequestEntity<MultiValueMap<String, String>> request =
                RequestEntity.post(URI.create("http://localhost:8081/oauth/token"))
                             .contentType(MediaType.MULTIPART_FORM_DATA)
                             .header(HttpHeaders.AUTHORIZATION, basic("authcodeclient", "authcodeclient12345678"))
                             .body(map);

        return restTemplate.exchange(request, Token.class).getBody().getAccess_token();
    }

    private String basic(String username, String password) {
        return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

因此從請求授權到最後第三方應用程式完成資源取得並處理之後,你會看到底下的結果:

Authorization Code 核發流程

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