Spring Security如何優(yōu)雅的增加OAuth2協(xié)議授權(quán)模式
一、什么是OAuth2協(xié)議?
OAuth 2.0 是一個關(guān)于授權(quán)的開放的網(wǎng)絡(luò)協(xié)議,是目前最流行的授權(quán)機(jī)制。
數(shù)據(jù)的所有者告訴系統(tǒng),同意授權(quán)第三方應(yīng)用進(jìn)入系統(tǒng),獲取這些數(shù)據(jù)。系統(tǒng)從而產(chǎn)生一個短期的進(jìn)入令牌(token),用來代替密碼,供第三方應(yīng)用使用。
由于授權(quán)的場景眾多,OAuth 2.0 協(xié)議定義了獲取令牌的四種授權(quán)方式,分別是:
- 授權(quán)碼模式:授權(quán)碼模式(authorization code)是功能最完整、流程最嚴(yán)密的授權(quán)模式。它的特點(diǎn)就是通過客戶端的后臺服務(wù)器,與"服務(wù)提供商"的認(rèn)證服務(wù)器進(jìn)行互動。
- 簡化模式:簡化模式(implicit grant type)不通過第三方應(yīng)用程序的服務(wù)器,直接在瀏覽器中向認(rèn)證服務(wù)器申請令牌,跳過了"授權(quán)碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認(rèn)證。
- 密碼模式:密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務(wù)商提供商"索要授權(quán)。
- 客戶端模式:客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務(wù)提供商"進(jìn)行認(rèn)證。嚴(yán)格地說,客戶端模式并不屬于OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務(wù)提供商"提供服務(wù),其實(shí)不存在授權(quán)問題。
四種授權(quán)模式分別使用不同的 grant_type 來區(qū)分
二、為什么要自定義授權(quán)類型?
雖然 OAuth2 協(xié)議定義了4種標(biāo)準(zhǔn)的授權(quán)模式,但是在實(shí)際開發(fā)過程中還是遠(yuǎn)遠(yuǎn)滿足不了各種變態(tài)的業(yè)務(wù)場景,需要我們?nèi)U(kuò)展。
例如增加圖形驗(yàn)證碼、手機(jī)驗(yàn)證碼、手機(jī)號密碼登錄等等的場景
而常見的做法都是通過增加 過濾器Filter 的方式來擴(kuò)展 Spring Security 授權(quán),但是這樣的實(shí)現(xiàn)方式有兩個問題:
- 脫離了
OAuth2的管理 - 不靈活:例如系統(tǒng)使用 密碼模式 授權(quán),網(wǎng)頁版需要增加圖形驗(yàn)證碼校驗(yàn),但是手機(jī)端APP又不需要的情況下,使用增加
Filter的方式去實(shí)現(xiàn)就比較麻煩了。
所以目前在 Spring Security 中比較優(yōu)雅和靈活的擴(kuò)展方式就是通過自定義 grant_type 來增加授權(quán)模式。
三、實(shí)現(xiàn)思路
在擴(kuò)展之前首先需要先了解 Spring Security 的整個授權(quán)流程,我以 密碼模式 為例去展開分析,如下圖所示

3.1. 流程分析
整個授權(quán)流程關(guān)鍵點(diǎn)分為以下兩個部分:
第一部分:關(guān)于授權(quán)類型 grant_type 的解析
- 每種
grant_type都會有一個對應(yīng)的TokenGranter實(shí)現(xiàn)類。 - 所有
TokenGranter實(shí)現(xiàn)類都通過CompositeTokenGranter中的tokenGranters集合存起來。 - 然后通過判斷
grantType參數(shù)來定位具體使用那個TokenGranter實(shí)現(xiàn)類來處理授權(quán)。
第二部分:關(guān)于授權(quán)登錄邏輯
- 每種
授權(quán)方式都會有一個對應(yīng)的AuthenticationProvider實(shí)現(xiàn)類來實(shí)現(xiàn)。 - 所有
AuthenticationProvider實(shí)現(xiàn)類都通過ProviderManager中的providers集合存起來。 TokenGranter類會 new 一個AuthenticationToken實(shí)現(xiàn)類,如UsernamePasswordAuthenticationToken傳給ProviderManager類。- 而
ProviderManager則通過AuthenticationToken來判斷具體使用那個AuthenticationProvider實(shí)現(xiàn)類來處理授權(quán)。
具體的登錄邏輯由 AuthenticationProvider 實(shí)現(xiàn)類來實(shí)現(xiàn),如 DaoAuthenticationProvider。
3.2. 擴(kuò)展分析
根據(jù)上面的流程,擴(kuò)展分為以下兩種場景
場景一:只對原有的授權(quán)邏輯進(jìn)行增強(qiáng)或者擴(kuò)展,如:用戶名密碼登錄前增加圖形驗(yàn)證碼校驗(yàn)。
該場景需要定義一個新的 grantType 類型,并新增對應(yīng)的 TokenGranter 實(shí)現(xiàn)類 添加擴(kuò)展內(nèi)容,然后加到 CompositeTokenGranter 中的 tokenGranters 集合里即可。
場景二:新加一種授權(quán)方式,如:手機(jī)號加密碼登錄。
該場景需要實(shí)現(xiàn)以下內(nèi)容:
- 定義一個新的
grantType類型,并新增對應(yīng)的TokenGranter實(shí)現(xiàn)類添加到CompositeTokenGranter中的tokenGranters集合里 - 新增一個
AuthenticationToken實(shí)現(xiàn)類,用于存放該授權(quán)所需的信息。 - 新增一個
AuthenticationProvider實(shí)現(xiàn)類 實(shí)現(xiàn)授權(quán)的邏輯,并重寫supports方法綁定步驟二的AuthenticationToken實(shí)現(xiàn)類
四、代碼實(shí)現(xiàn)
下面以 場景二 新增手機(jī)號加密碼授權(quán)方式為例,展示核心的代碼實(shí)現(xiàn)
4.1. 創(chuàng)建 AuthenticationToken 實(shí)現(xiàn)類
創(chuàng)建 MobileAuthenticationToken 類,用于存儲手機(jī)號和密碼信息
public class MobileAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
public MobileAuthenticationToken(String mobile, String password) {
super(null);
this.principal = mobile;
this.credentials = password;
setAuthenticated(false);
}
public MobileAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
4.2. 創(chuàng)建 AuthenticationProvider 實(shí)現(xiàn)類
創(chuàng)建 MobileAuthenticationProvider 類,實(shí)現(xiàn)登錄邏輯,并綁定 MobileAuthenticationToken 類
@Setter
public class MobileAuthenticationProvider implements AuthenticationProvider {
private ZltUserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
MobileAuthenticationToken authenticationToken = (MobileAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
String password = (String) authenticationToken.getCredentials();
UserDetails user = userDetailsService.loadUserByMobile(mobile);
if (user == null) {
throw new InternalAuthenticationServiceException("手機(jī)號或密碼錯誤");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("手機(jī)號或密碼錯誤");
}
MobileAuthenticationToken authenticationResult = new MobileAuthenticationToken(user, password, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return MobileAuthenticationToken.class.isAssignableFrom(authentication);
}
}
4.3. 創(chuàng)建 TokenGranter 實(shí)現(xiàn)類
創(chuàng)建 MobilePwdGranter 類并定義 grant_type 的值為 mobile_password
public class MobilePwdGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "mobile_password";
private final AuthenticationManager authenticationManager;
public MobilePwdGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String password = parameters.get("password");
parameters.remove("password");
Authentication userAuth = new MobileAuthenticationToken(mobile, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
userAuth = authenticationManager.authenticate(userAuth);
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
4.4. 加到 CompositeTokenGranter 中的集合里
// 添加手機(jī)號加密碼授權(quán)模式 tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
4.5. 測試
使用以下地址,指定 grant_type 為 mobile_password 進(jìn)行授權(quán)獲取 access_token
/oauth/token?grant_type=mobile_password&mobile={mobile}&password={password}

五、參考樣例
詳細(xì)的代碼實(shí)現(xiàn)可以參考
https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-uaa
到此這篇關(guān)于Spring Security如何優(yōu)雅的增加OAuth2協(xié)議授權(quán)模式的文章就介紹到這了,更多相關(guān)Spring Security OAuth2內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity 自定義表單登錄的實(shí)現(xiàn)
- 解析SpringSecurity自定義登錄驗(yàn)證成功與失敗的結(jié)果處理問題
- spring security自定義認(rèn)證登錄的全過程記錄
- spring security自定義登錄頁面
- Spring Security保護(hù)用戶密碼常用方法詳解
- Spring Security UserDetails實(shí)現(xiàn)原理詳解
- SpringSecurity自定義成功失敗處理器的示例代碼
- Spring Security實(shí)現(xiàn)不同接口安全策略方法詳解
- Spring Security自定義登錄原理及實(shí)現(xiàn)詳解
相關(guān)文章
springMVC中@RequestParam和@RequestPart的區(qū)別
本文主要介紹了springMVC中@RequestParam和@RequestPart的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
JavaWeb?使用DBUtils實(shí)現(xiàn)增刪改查方式
這篇文章主要介紹了JavaWeb?使用DBUtils實(shí)現(xiàn)增刪改查方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java Hutool 包工具類推薦 ExcelUtil詳解
這篇文章主要介紹了Java Hutool 包工具類推薦 ExcelUtil詳解,需要引入hutool包,版本號可根據(jù)實(shí)際情況更換,除hutool包之外,還需要引入操作Excel必要包,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
總結(jié)Java常用加解密方法AES?SHA1?md5
這篇文章主要為大家介紹了Java常用加密方法AES?SHA1?md5總結(jié)及示例demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
SpringBoot調(diào)用第三方WebService接口的兩種方法
本文主要介紹了SpringBoot調(diào)用第三方WebService接口的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06

