Spring Security OAuth2 Demo —— 密碼模式(Password)
前情回顧
前幾節分享了OAuth2的流程與授權碼模式和隱式授權模式兩種的Demo,我們瞭解到授權碼模式是OAuth2四種模式流程最複雜模式,複雜程度由大至小:授權碼模式 > 隱式授權模式 > 密碼模式 > 客戶端模式
其中密碼模式的流程是:讓使用者填寫表單提交到授權伺服器,表單中包含使用者的使用者名稱、密碼、客戶端的id和金鑰的加密串,授權伺服器先解析並校驗客戶端資訊,然後校驗使用者資訊,完全通過返回access_token,否則預設都是401 http狀態碼,提示未授權無法訪問
本文目標
編寫與說明密碼模式的Spring Security Oauth2的demo實現,讓未了解過相關知識的讀者對此授權流程有個更直觀的概念
以下分成授權伺服器與資源伺服器分別進行解釋,只講關鍵部分,詳情見Github:https://github.com/hellxz/spring-security-oauth2-learn
搭建密碼模式授權伺服器
程式碼結構與之前兩個模式相同,這裡便不再進行說明
SecurityConfig配置
package com.github.hellxz.oauth2.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collections; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("hellxz") .password(passwordEncoder().encode("xyz")) .authorities(new ArrayList<>(0)); } @Override protected void configure(HttpSecurity http) throws Exception { //所有請求必須認證 http.authorizeRequests().anyRequest().authenticated(); } }
基本的SpringSecurity的配置,開啟Spring Security的Web安全功能,填了一個使用者資訊,所有資源必須經過授權才可以訪問
AuthorizationConfig授權伺服器配置
package com.github.hellxz.oauth2.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; 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 AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager;//密碼模式需要注入認證管理器 @Autowired public PasswordEncoder passwordEncoder; //配置客戶端 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //@formatter:off clients.inMemory() .withClient("client-a") .secret(passwordEncoder.encode("client-a-secret")) .authorizedGrantTypes("password") //主要是這裡,開始了密碼模式 .scopes("read_scope"); //@formatter:on } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager);//密碼模式必須新增authenticationManager } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients() .checkTokenAccess("isAuthenticated()"); } }
這裡開啟了授權伺服器的功能,與上幾篇文章中不同的是注入了AuthenticationManager,以及在configure(AuthorizationServerEndpointsConfigurer endpoints)方法中設定了AuthenticationManager
application.properties配置sever.port=8080
搭建資源伺服器
這裡的關鍵就是ResourceConfig,配置比較簡單與其它幾個模式完全一致,模式的不同主要表現在授權伺服器與客戶端伺服器上,資源伺服器只做token的校驗與給予資源
package com.github.hellxz.oauth2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Primary
@Bean
public RemoteTokenServices remoteTokenServices() {
final RemoteTokenServices tokenServices = new RemoteTokenServices();
//設定授權伺服器check_token端點完整地址
tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
//設定客戶端id與secret,注意:client_secret值不能使用passwordEncoder加密!
tokenServices.setClientId("client-a");
tokenServices.setClientSecret("client-a-secret");
return tokenServices;
}
@Override
public void configure(HttpSecurity http) throws Exception {
//設定建立session策略
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//@formatter:off
//所有請求必須授權
http.authorizeRequests()
.anyRequest().authenticated();
//@formatter:on
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("resource1").stateless(true);
}
}
ResourceController主要接收一個使用者名稱,返回一個username與email的json串
application.properties設定server.port=8081
準備工作到這裡就差不多了,開始測試
測試流程
這裡客戶端使用postman手動傳送請求進行演示
訪問/oauth/token端點,獲取token
http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_scope&grant_type=password
請求頭:
請求體:
token返回值
{ "access_token": "4a3c351d-770d-42aa-af39-3f54b50152e9", "token_type": "bearer", "expires_in": 43199, "scope": "read_scope" }
使用token呼叫資源,訪問http://localhost:8081/user/hellxz001,注意使用token新增Bearer請求頭
相當於在Headers中新增 Authorization:Bearer 4a3c351d-770d-42aa-af39-3f54b50152e9
資源正確返回
尾聲
本文僅說明密碼模式的精簡化配置,某些部分如資源服務再訪問授權服務去校驗token這部分生產環境可能會換成Jwt、Redis等tokenStore實現,授權伺服器中的使用者資訊與客戶端資訊生產環境應從資料庫中讀取,對應Spring Security的UserDetailsService實現類或使用者資訊的Provider等
最近發現部落格寫得相對較長,一方面有相當大的重複解釋程式碼的部分,另一方面是程式碼很多不關鍵的地方也直接全貼出來了,部落格長了程式碼全了,讀者容易失去閱讀的興趣與探索實踐的慾望。程式碼不全會直接貼出原始碼地址,暫時就這樣,把這幾篇關於OAuth2授權模式demo的文章趕出來
程式碼早就寫完了,下週可能要開始加班了,先把這些已經完成的部分寫出來,後續有什麼新的知識點才有時間記