3. 授權碼模式
示例代碼對應倉庫:
本小節,我們來學習授權碼模式(Authorization Code)。
授權碼模式,是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后台服務器,與授權務器進行互動。
一般情況下,在有客戶端的情況下,我們與第三方平台常常采用這種方式。

- (A)用戶訪問客戶端,后者將前者跳轉到到授權服務器。
- (B)用戶選擇是否給予客戶端授權。
- (C)假設用戶給予授權,授權服務器將跳轉到客戶端事先指定的"重定向 URI"(Redirection URI),同時附上一個授權碼。
- (D)客戶端收到授權碼,附上早先的"重定向 URI",向認證服務器申請令牌。這一步是在客戶端的后台的服務器上完成的,對用戶不可見。
- (E)認證服務器核對了授權碼和重定向 URI,確認無誤后,向客戶端發送訪問令牌。
下面,我們來新建兩個項目,搭建一個授權碼模式的使用示例。如下圖所示:

package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 用戶認證 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
//配置使用的 AuthenticationManager 實現用戶認證的功能
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
//設置 /oauth/check_token 端點,通過認證后可訪問。
//這里的認證,指的是使用 client-id + client-secret 進行的客戶端認證,不要和用戶認證混淆。
//其中,/oauth/check_token 端點對應 CheckTokenEndpoint 類,用於校驗訪問令牌的有效性。
//在客戶端訪問資源服務器時,會在請求中帶上訪問令牌。
//在資源服務器收到客戶端的請求時,會使用請求中的訪問令牌,找授權服務器確認該訪問令牌的有效性。
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("isAuthenticated()");
}
//進行 Client 客戶端的配置。
//設置使用基於內存的 Client 存儲器。實際情況下,最好放入數據庫中,方便管理。
/*
*
* 創建一個 Client 配置。如果要繼續添加另外的 Client 配置,可以在 <4.3> 處使用 #and() 方法繼續拼接。
* 注意,這里的 .withClient("clientapp").secret("112233") 代碼段,就是 client-id 和 client-secret。
*補充知識:可能會有胖友會問,為什么要創建 Client 的 client-id 和 client-secret 呢?
*通過 client-id 編號和 client-secret,授權服務器可以知道調用的來源以及正確性。這樣,
*即使“壞人”拿到 Access Token ,但是沒有 client-id 編號和 client-secret,也不能和授權服務器發生有效的交互。
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // <4.1>
.withClient("clientapp").secret("112233") // <4.2> Client 賬號、密碼。
.authorizedGrantTypes("authorization_code") // <4.2> 授權碼模式
.redirectUris("http://127.0.0.1:9090/callback")
.scopes("read_userinfo", "read_contacts") // <4.2> 可授權的 Scope
// .and().withClient() // <4.3> 可以繼續配置新的 Client
;
}
}
僅僅需要修改 OAuth2AuthorizationServerConfig 類,設置使用 "authorization_code" 授權碼模式,並設置回調地址。
注意,這里設置的回調地址,稍后我們會在「3.2 搭建資源服務器」中實現。
3.1.1 簡單測試
執行 AuthorizationServerApplication 啟動授權服務器。
① 使用瀏覽器,訪問 http://127.0.0.1:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://127.0.0.1:9090/callback&response_type=code&scope=read_userinfo 地址,獲取授權。請求參數說明如下:
client_id參數,必傳,為我們在 OAuth2AuthorizationServer 中配置的 Client 的編號。
redirect_uri參數,可選,回調地址。當然,如果client_id對應的 Client 未配置redirectUris屬性,會報錯。
response_type參數,必傳,返回結果為code授權碼。
scope參數,可選,申請授權的 Scope 。如果多個,使用逗號分隔。
state參數,可選,表示客戶端的當前狀態,可以指定任意值,授權服務器會原封不動地返回這個值。
友情提示:state 參數,未在上述 URL 中體現出來。
因為我們並未登錄授權服務器,所以被攔截跳轉到登錄界面。如下圖所示:

② 輸入用戶的賬號密碼「yunai/1024」進行登錄。登錄完成后,進入授權界面。如下圖所示:
和我們日常使用的騰訊 QQ、微信、微博等等三方登錄,是一模一樣的,除了丑了點,嘿嘿~

③ 選擇 scope.read_userinfo 為 Approve 允許,點擊「Authorize」按鈕,完成授權操作。瀏覽器自動重定向到 Redirection URI 地址,並且在 URI 上可以看到 code 授權碼。如下圖所示:

友情提示:/oauth/authorize 對應 AuthorizationEndpoint 端點。
④ 因為我們暫時沒有啟動資源服務器,所以顯示無法訪問。這里,我們先使用 Postman 模擬請求 http://localhost:8080/oauth/token 地址,使用授權碼獲取到訪問令牌。如下圖所示:


請求說明:
- 通過 Basic Auth 的方式,填寫
client-id+client-secret作為用戶名與密碼,實現 Client 客戶端有效性的認證。
- 請求參數
grant_type為"authorization_code",表示使用授權碼模式。
- 請求參數
code,從授權服務器獲取到的授權碼。
- 請求參數
redirect_uri,Client 客戶端的 Redirection URI 地址。
注意,授權碼僅能使用一次,重復請求會報 Invalid authorization code: 錯誤。如下圖所示:

3.2 搭建資源服務器
復制 lab-68-demo02-resource-server 項目,主要是提供回調地址。如下圖所示:

① 新建 CallbackController 類,提供 /callback 回調地址。
② 在 OAuth2ResourceServerConfig 配置類中,設置 /callback 回調地址無需權限驗證,不然回調都跳轉不過來哈。
3.2.1 CallbackController
創建 CallbackController 類,提供 /callback 回調地址,在獲取到授權碼時,請求授權服務器,通過授權碼獲取訪問令牌。代碼如下:
@RestController
@RequestMapping("/")
public class CallbackController {
@Autowired
private OAuth2ClientProperties oauth2ClientProperties;
@Value("${security.oauth2.access-token-uri}")
private String accessTokenUri;
@GetMapping("/callback")
public OAuth2AccessToken login(@RequestParam("code") String code) {
// 創建 AuthorizationCodeResourceDetails 對象
AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
resourceDetails.setAccessTokenUri(accessTokenUri);
resourceDetails.setClientId(oauth2ClientProperties.getClientId());
resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());
// 創建 OAuth2RestTemplate 對象
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails);
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAuthorizationCode(code); // <1> 設置 code
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setPreservedState("http://127.0.0.1:9090/callback"); // <2> 通過這個方式,設置 redirect_uri 參數
restTemplate.setAccessTokenProvider(new AuthorizationCodeAccessTokenProvider());
// 獲取訪問令牌
return restTemplate.getAccessToken();
}
}
代碼比較簡單,還是使用 OAuth2RestTemplate 進行請求授權服務器,胖友自己瞅瞅哈。
需要注意的是 <1> 和 <2> 處,設置請求授權服務器需要的 code 和 redirect_uri 參數。
3.2.2 簡單測試
執行 ResourceServerApplication 啟動資源服務器。
重復「3.2.1 簡單測試」的過程,成功獲取到訪問令牌。如下圖所示:

