5. 客戶端模式
客戶端模式,指客戶端以自己的名義,而不是以用戶的名義,向授權服務器進行認證。
嚴格地說,客戶端模式並不屬於 OAuth 框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求授權服務器提供服務,其實不存在授權問題。
旁白君:我們對接微信公眾號時,就采用的客戶端模式。我們的后端服務器就扮演“客戶端”的角色,與微信公眾號的后端服務器進行交互。

- (A)客戶端向授權服務器進行身份認證,並要求一個訪問令牌。
- (B)授權服務器確認無誤后,向客戶端提供訪問令牌。
下面,我們來新建兩個項目,搭建一個客戶端模式的使用示例。如下圖所示:

5.1 搭建授權服務器
復制出 lab-68-demo02-authorization-server-with-client-credentials 項目,修改搭建授權服務器。改動點如下圖所示:

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 用戶認證 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
//配置使用的 AuthenticationManager 實現用戶認證的功能
@Bean
public static NoOpPasswordEncoder passwordEncodeF(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
//設置 /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("client_credentials") // <4.2> 客戶端模式
.redirectUris("http://127.0.0.1:9090/callback02")
.scopes("read_userinfo", "read_contacts") // <4.2> 可授權的 Scope
// .and().withClient() // <4.3> 可以繼續配置新的 Client
;
}
}
① 刪除 SecurityConfig 配置類,因為客戶端模式下,無需 Spring Security 提供用戶的認證功能。
但是,Spring Security OAuth 需要一個 PasswordEncoder Bean,否則會報錯,因此我們在 OAuth2AuthorizationServerConfig 類的 #passwordEncoder() 方法進行創建。
② 修改 OAuth2AuthorizationServerConfig 類,設置使用 "client_credentials" 客戶端模式。
5.1.1 簡單測試
執行 AuthorizationServerApplication 啟動授權服務器。下面,我們使用 Postman 模擬一個 Client。
① POST 請求 http://localhost:8080/oauth/token 地址,使用客戶端模式進行授權。如下圖所示:


請求說明:
- 通過 Basic Auth 的方式,填寫
client-id+client-secret作為用戶名與密碼,實現 Client 客戶端有效性的認證。
- 請求參數
grant_type為"client_credentials",表示使用客戶端模式。
響應就是訪問令牌,胖友自己瞅瞅即可。
5.2 搭建資源服務器
復制 lab-68-demo02-resource-server 項目,修改點如下圖所示:

① 新建 ClientLoginController 類,提供 /client-login 接口,實現調用授權服務器,進行客戶端模式的授權,獲得訪問令牌。代碼如下:
@RestController
@RequestMapping("/")
public class ClientLoginController {
@Autowired
private OAuth2ClientProperties oauth2ClientProperties;
@Value("${security.oauth2.access-token-uri}")
private String accessTokenUri;
@PostMapping("/client-login")
public OAuth2AccessToken login() {
// 創建 ClientCredentialsResourceDetails 對象
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(accessTokenUri);
resourceDetails.setClientId(oauth2ClientProperties.getClientId());
resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());
// 創建 OAuth2RestTemplate 對象
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails);
restTemplate.setAccessTokenProvider(new ClientCredentialsAccessTokenProvider());
// 獲取訪問令牌
return restTemplate.getAccessToken();
}
}
代碼比較簡單,還是使用 OAuth2RestTemplate 進行請求授權服務器,胖友自己瞅瞅哈。
② 在 OAuth2ResourceServerConfig 配置類中,設置 /client-login 接口無需權限驗證,不然無法調用哈。
5.2.1 簡單測試
執行 ResourceServerApplication 啟動資源服務器。
① 使用「5.1.1 簡單測試」小節獲得的訪問令牌,請求 <127.0.0.1:9090/api/example/hello> 接口時帶上,則請求會被通過。如下圖所示:

② 請求 http://127.0.0.1:9090/client-login 接口,使用客戶端模式進行授權,獲得訪問令牌。如下圖所示:

響應結果和授權服務器的 /oauth/token 接口是一致的,因為就是調用它,嘿嘿~
