Spring security OAuth2 深入解析
一、OAuth2 概要
1.1.OAuth2基本流程
話不多說,先上圖:

分析一波:
- client:第三方應(yīng)用(即App或向外提供接口)
- Resource Owner:資源所有者(即用戶)
- Authentication Server:授權(quán)認(rèn)證服務(wù)(發(fā)配Access Token)
- Resource Server:資源服務(wù)器(存儲(chǔ)用戶資源信息等資源)
其實(shí)不管微信或者QQ大體上都是使用這種OAuth2的基本流程:
- 第三方應(yīng)用請(qǐng)求用戶授權(quán);
- 用戶同意授權(quán),并返回一個(gè)授權(quán)碼(code);
- 第三方應(yīng)用根據(jù)授權(quán)碼(code)向授權(quán)認(rèn)證服務(wù)進(jìn)行授權(quán);
- 授權(quán)服務(wù)器根據(jù)授權(quán)碼(code),校驗(yàn)通過,并返回給第三方應(yīng)用令牌(Access Token);
- 第三方應(yīng)用根據(jù)令牌(Access Token)向資源服務(wù)請(qǐng)求相關(guān)資源;
- 資源服務(wù)器驗(yàn)證令牌(Access Token),校驗(yàn)通過,并返回第三方所請(qǐng)求的資源。
1.2.服務(wù)類型
OAuth2 在服務(wù)提供者上可分為兩類:
- 授權(quán)認(rèn)證服務(wù):AuthenticationServer
@Configuration
@EnableAuthorizationServer
public class CustomAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter
- 資源獲取服務(wù):ResourceServer
@Configuration
@EnableResourceServer
public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
注:這兩者有時(shí)候可能存在同一個(gè)應(yīng)用程序中(即SOA架構(gòu))。在Spring OAuth中可以簡(jiǎn)便的將其分配到兩個(gè)應(yīng)用中(即微服務(wù)),而且可多個(gè)資源獲取服務(wù)共享一個(gè)授權(quán)認(rèn)證服務(wù)。
1.3.授權(quán)認(rèn)證服務(wù)
主要的操作:
- 獲取第三方應(yīng)用發(fā)送的授權(quán)碼(code)以及第三方應(yīng)用標(biāo)識(shí)
- 根據(jù)授權(quán)碼及標(biāo)識(shí)進(jìn)行校驗(yàn)
- 校驗(yàn)通過,發(fā)送令牌(Access Token)
分析一波:
1)第一步操作:
- 授權(quán)碼(code):第三方應(yīng)用進(jìn)行第一步“Authorization Request”時(shí),請(qǐng)求參數(shù)redirect_uri中的回調(diào)鏈接,服務(wù)會(huì)生成相關(guān)用戶憑證,并在其回調(diào)鏈接上附帶code
-
第三方用戶標(biāo)識(shí):
- client_id: 第三方用戶的id(可理解為賬號(hào))
- client_secret:第三方應(yīng)用和授權(quán)服務(wù)器之間的安全憑證(可理解為密碼)
注:其中client_id和client_secret都是授權(quán)服務(wù)器發(fā)送給第三方應(yīng)用的,如:微信等一系列授權(quán),在其平臺(tái)上注冊(cè),獲取其appid和secret同樣道理(個(gè)人理解為賬號(hào)密碼)。
既然是賬號(hào)秘密,總不能以get請(qǐng)求,也太不安全了。因此,OAuth2要求該請(qǐng)求必須是POST請(qǐng)求,同時(shí),還必須時(shí)HTTPS服務(wù),以此保證獲取到的安全憑證(Access Token)的安全性。
2)第二步操作:
- 授權(quán)認(rèn)證服務(wù)器根據(jù)標(biāo)識(shí)校驗(yàn)第三方應(yīng)用的真實(shí)性
- 授權(quán)認(rèn)證服務(wù)器根據(jù)授權(quán)碼(code)進(jìn)行校驗(yàn)用戶憑證
3)第三步操作:
- 生成Access Token(MD5類型,uuid類型,jwt類型等)
1.4.資源獲取服務(wù)
主要的操作:
- 校驗(yàn)Access Token
- 發(fā)放資源信息
二、Spring Security OAuth2的使用
1.授權(quán)認(rèn)證服務(wù)
spring OAuth2中,我們配置一個(gè)授權(quán)認(rèn)證服務(wù),我們最主要有以下三點(diǎn):
- 第三方用戶客戶端詳情 → Client
- 令牌的生成管理 → Access Token
- 端點(diǎn)接入 → endpoints
spring中有三個(gè)配置與這三點(diǎn)一一對(duì)應(yīng):
- ClientDetailsServiceConfigurer:用來配置客戶端詳情服務(wù)。
- AuthorizationServerSecurityConfigurer:用來配置令牌端點(diǎn)(Token Endpoint)的安全約束.
- AuthorizationServerEndpointsConfigurer:來配置授權(quán)(authorization)以及令牌(token)的訪問端點(diǎn)和令牌服務(wù)(token services)
1.1.第三方用戶客戶端詳情
除了上面說到的client_id和client_secret,還需要一些服務(wù)附帶一些授權(quán)認(rèn)證參數(shù)。
1).Grant Type
其實(shí)OAuth2不僅提供授權(quán)碼(code)這種格式授權(quán)方式,還提供幾個(gè)其他類型。其中用Grant Type代表當(dāng)前授權(quán)的類型。 Grant Type包括:
- authorization_code:傳統(tǒng)的授權(quán)碼模式
- implicit:隱式授權(quán)模式
- password:資源所有者(即用戶)密碼模式
- client_credentials:客戶端憑據(jù)(客戶端ID以及Key)模式
- refresh_token:獲取access token時(shí)附帶的用于刷新新的token模式
2).scope
其實(shí)授權(quán)賦予第三方用戶可以在資源服務(wù)器獲取資源,經(jīng)常就是調(diào)取Api請(qǐng)求附帶令牌,然而調(diào)取api有增刪查改等功能,而scopes的值就是all(全部權(quán)限),read,write等權(quán)限。就是第三方訪問資源的一個(gè)權(quán)限,訪問范圍。
3).accessTokenValiditySeconds
還可以設(shè)置accessTokenValiditySeconds屬性來設(shè)置Access Token的存活時(shí)間。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("catalpaFlat")
.secret("catalpaFlat-secret")
.accessTokenValiditySeconds(7200)
.authorizedGrantTypes("refresh_token","password")
.scopes("all");
}
1.2.令牌的生成和管理
AccessToken的存在意義:
- 創(chuàng)建AccessToken,并保存,以備后續(xù)請(qǐng)求訪問都可以認(rèn)證成功并獲取到資源
- AccessToken還有一個(gè)潛在功能,就是使用jwt生成token時(shí)候,可以用來加載一些信息,把一些相關(guān)權(quán)限等包含在AccessToken中
1).AuthorizationServerTokenServices
AuthorizationServerTokenServices 提供了對(duì)AccessToken的相關(guān)操作創(chuàng)建、刷新、獲取。
public interface AuthorizationServerTokenServices {
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}
2).DefaultTokenServices
AuthorizationServerTokenServices竟然可以操作AccessToken,那么OAuth2就默認(rèn)為我們提供了一個(gè)默認(rèn)的DefaultTokenServices。包含了一些有用實(shí)現(xiàn),可以使用它來修改令牌的格式和令牌的存儲(chǔ)等,但是生成的token是隨機(jī)數(shù)。
3).TokenStore
創(chuàng)建AccessToken完之后,除了發(fā)放給第三方,肯定還得保存起來,才可以使用。因此,TokenStore為我們完成這一操作,將令牌(AccessToken)保存或持久化。
TokenStore也有一個(gè)默認(rèn)的實(shí)現(xiàn)類InMemoryTokenStore,從名字就知道是通過保存到內(nèi)存進(jìn)而實(shí)現(xiàn)保存Access Token。
TokenStore的實(shí)現(xiàn)有多種類型,可以根據(jù)業(yè)務(wù)需求更改Access Token的保存類型:
- InMemoryTokenStore:這個(gè)是OAuth2默認(rèn)采用的實(shí)現(xiàn)方式。在單服務(wù)上可以體現(xiàn)出很好特效(即并發(fā)量不大,并且它在失敗的時(shí)候不會(huì)進(jìn)行備份),大多項(xiàng)目都可以采用此方法。畢竟存在內(nèi)存,而不是磁盤中,調(diào)試簡(jiǎn)易。
- JdbcTokenStore:這個(gè)是基于JDBC的實(shí)現(xiàn),令牌(Access Token)會(huì)保存到數(shù)據(jù)庫。這個(gè)方式,可以在多個(gè)服務(wù)之間實(shí)現(xiàn)令牌共享。
- JwtTokenStore:jwt全稱 JSON Web Token。這個(gè)實(shí)現(xiàn)方式不用管如何進(jìn)行存儲(chǔ)(內(nèi)存或磁盤),因?yàn)樗梢园严嚓P(guān)信息數(shù)據(jù)編碼存放在令牌里。JwtTokenStore 不會(huì)保存任何數(shù)據(jù),但是它在轉(zhuǎn)換令牌值以及授權(quán)信息方面與 DefaultTokenServices 所扮演的角色是一樣的。但有兩個(gè)缺點(diǎn):
- 撤銷一個(gè)已經(jīng)授權(quán)的令牌會(huì)很困難,因此只適用于處理一個(gè)生命周期較短的以及撤銷刷新令牌。
- 令牌占用空間大,如果加入太多用戶憑證信息,會(huì)存在傳輸冗余
4).JWT Token
想使用jwt令牌,需要在授權(quán)服務(wù)中配置JwtTokenStore。之前說了,jwt將一些信息數(shù)據(jù)編碼后存放在令牌,那么其實(shí)在傳輸?shù)臅r(shí)候是很不安全的,所以Spring OAuth2提供了JwtAccessTokenConverter來懟令牌進(jìn)行編碼和解碼。適用JwtAccessTokenConverter可以自定義秘簽(SigningKey)。SigningKey用處就是在授權(quán)認(rèn)證服務(wù)器生成進(jìn)行簽名編碼,在資源獲取服務(wù)器根據(jù)SigningKey解碼校驗(yàn)。
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey("CatalpaFlat")

1.3.端點(diǎn)接入-endpoints
授權(quán)認(rèn)證是使用AuthorizationEndpoint這個(gè)端點(diǎn)來進(jìn)行控制,一般使用AuthorizationServerEndpointsConfigurer 來進(jìn)行配置。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}
1).端點(diǎn)(endpoints)的相關(guān)屬性配置:
- authenticationManager:認(rèn)證管理器。若我們上面的Grant Type設(shè)置為password,則需設(shè)置一個(gè)AuthenticationManager對(duì)象
- userDetailsService:若是我們實(shí)現(xiàn)了UserDetailsService,來管理用戶信息,那么得設(shè)我們的userDetailsService對(duì)象
- authorizationCodeServices:授權(quán)碼服務(wù)。若我們上面的Grant Type設(shè)置為authorization_code,那么得設(shè)一個(gè)AuthorizationCodeServices對(duì)象
- tokenStore:這個(gè)就是我們上面說到,把我們想要是實(shí)現(xiàn)的Access Token類型設(shè)置
- accessTokenConverter:Access Token的編碼器。也就是JwtAccessTokenConverter
- tokenEnhancer:token的拓展。當(dāng)使用jwt時(shí)候,可以實(shí)現(xiàn)TokenEnhancer來進(jìn)行jwt對(duì)包含信息的拓展
- tokenGranter:當(dāng)默認(rèn)的Grant Type已經(jīng)不夠我們業(yè)務(wù)邏輯,實(shí)現(xiàn)TokenGranter 接口,授權(quán)將會(huì)由我們控制,并且忽略Grant Type的幾個(gè)屬性。
2).端點(diǎn)(endpoints)的授權(quán)url:
要授權(quán)認(rèn)證,肯定得由url請(qǐng)求,才可以傳輸。因此OAuth2提供了配置授權(quán)端點(diǎn)的URL。
AuthorizationServerEndpointsConfigurer ,還是這個(gè)配置對(duì)象進(jìn)行配置,其中由一個(gè)pathMapping()方法進(jìn)行配置授權(quán)端點(diǎn)URL路徑,默認(rèn)提供了兩個(gè)參數(shù)defaultPath和customPath:
public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath) {
this.patternMap.put(defaultPath, customPath);
return this;
}
pathMapping的defaultPath有:
- /oauth/authorize:授權(quán)端點(diǎn)
- /oauth/token:令牌端點(diǎn)
- /oauth/confirm_access:用戶確認(rèn)授權(quán)提交端點(diǎn)
- /oauth/error:授權(quán)服務(wù)錯(cuò)誤信息端點(diǎn)
- /oauth/check_token:用于資源服務(wù)訪問的令牌解析端點(diǎn)
- /oauth/token_key:提供公有密匙的端點(diǎn),如果使用JWT令牌的話
注:pathMapping的兩個(gè)參數(shù)都將以 "/" 字符為開始的字符串
1.4.自定義錯(cuò)誤處理(Error Handling)
實(shí)際上我們上面說到的端點(diǎn),其實(shí)可以看成Controller,用于返回不同端點(diǎn)的響應(yīng)內(nèi)容。
授權(quán)服務(wù)的錯(cuò)誤信息是使用標(biāo)準(zhǔn)的Spring MVC來進(jìn)行處理的,也就是 @ExceptionHandler 注解的端點(diǎn)方法,我們可以提供一個(gè) WebResponseExceptionTranslator 對(duì)象。最好的方式是改變響應(yīng)的內(nèi)容而不是直接進(jìn)行渲染。
- 假如說在呈現(xiàn)令牌端點(diǎn)的時(shí)候發(fā)生了異常,那么異常委托了 HttpMessageConverters 對(duì)象(它能夠被添加到MVC配置中)來進(jìn)行輸出。
- 假如說在呈現(xiàn)授權(quán)端點(diǎn)的時(shí)候未通過驗(yàn)證,則會(huì)被重定向到 /oauth/error 即錯(cuò)誤信息端點(diǎn)中。whitelabel error (即Spring框架提供的一個(gè)默認(rèn)錯(cuò)誤頁面)錯(cuò)誤端點(diǎn)提供了HTML的響應(yīng),但是我們大概可能需要實(shí)現(xiàn)一個(gè)自定義錯(cuò)誤頁面(例如只是簡(jiǎn)單的增加一個(gè) @Controller 映射到請(qǐng)求路徑上 @RequestMapping("/oauth/error"))。
2.資源獲取服務(wù)
資源服務(wù)器,其實(shí)就是存放一些受令牌保護(hù)的資源,只有令牌并且有效正確才能獲取到資源。 內(nèi)部是通過Spring OAuth2的Spring Security Authentication filter 的過濾鏈來進(jìn)行保護(hù)。
2.1.ResourceServerConfigurerAdapter
我們可以繼承ResourceServerConfigurerAdapter,來使用 ResourceServerSecurityConfigurer進(jìn)行相關(guān)配置。
public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
}
2.2.ResourceServerSecurityConfigurer的相關(guān)屬性
- tokenServices:ResourceServerTokenServices 類的實(shí)例,用來實(shí)現(xiàn)令牌服務(wù)。
- resourceId:這個(gè)資源服務(wù)的ID,這個(gè)屬性是可選的,但是推薦設(shè)置并在授權(quán)服務(wù)中進(jìn)行驗(yàn)證。
- tokenExtractor 令牌提取器用來提取請(qǐng)求中的令牌。
- 請(qǐng)求匹配器,用來設(shè)置需要進(jìn)行保護(hù)的資源路徑,默認(rèn)的情況下是受保護(hù)資源服務(wù)的全部路徑。
- 受保護(hù)資源的訪問規(guī)則,默認(rèn)的規(guī)則是簡(jiǎn)單的身份驗(yàn)證(plain authenticated)。
- 其他的自定義權(quán)限保護(hù)規(guī)則通過 HttpSecurity 來進(jìn)行配置。
2.3.ResourceServerTokenServices
ResourceServerTokenServices 是組成授權(quán)服務(wù)的另一半。
1).若是資源服務(wù)器和授權(quán)服務(wù)在同一個(gè)應(yīng)用,可以使用DefaultTokenServices
2).若是分離的。ResourceServerTokenServices必須知道令牌的如何解碼。
ResourceServerTokenServices解析令牌的方法:
- 使用RemoteTokenServices,資源服務(wù)器通過HTTP請(qǐng)求來解碼令牌。每次都請(qǐng)求授權(quán)服務(wù)器的端點(diǎn)-/oauth/check_toke,以此來解碼令牌
- 若是訪問量大,則通過http獲取之后,換成令牌的結(jié)果
- 若是jwt令牌,需請(qǐng)求授權(quán)服務(wù)的/oauth/token_key,來獲取key進(jìn)行解碼
注:授權(quán)認(rèn)證服務(wù)需要把/oauth/check_toke暴露出來,并且附帶上權(quán)限訪問。
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
(~ ̄▽ ̄)~未完待續(xù)... ...