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 簡單測試」的過程,成功獲取到訪問令牌。如下圖所示: