概述
在 OAuth2 體系中認(rèn)證通過后返回的令牌信息分為兩大類:不透明令牌(opaque tokens) 和 透明令牌(not opaque tokens)酿箭。
不透明令牌 就是一種無可讀性的令牌窟扑,一般來說就是一段普通的 UUID 字符串。使用不透明令牌會降低系統(tǒng)性能和可用性,并且增加延遲官套,因為資源服務(wù)不知道這個令牌是什么,代表誰片挂,需要調(diào)用認(rèn)證服務(wù)器獲取用戶信息接口睬魂,如下就是我們在資源服務(wù)器中的配置终吼,需要指明認(rèn)證服務(wù)器的接口地址。
security:
oauth2:
resource:
user-info-uri: http://localhost:5000/user/current/get
id: account-service
透明令牌的典型代表就是 JWT 了氯哮,用戶信息保存在 JWT 字符串中际跪,資源服務(wù)器自己可以解析令牌不再需要去認(rèn)證服務(wù)器校驗令牌。
之前的章節(jié)中我們是使用了不透明令牌access_token,但考慮到在微服務(wù)體系中這種中心化的授權(quán)服務(wù)會成為瓶頸姆打,本章我們就使用jwt來替換之前的access_token良姆,專(zhuang)業(yè)(bi)點就叫去中心化。
jwt 是什么
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)(RFC 7519)幔戏。該token被設(shè)計為緊湊且安全的玛追,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息闲延,以便于從資源服務(wù)器獲取資源痊剖,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證慨代,也可被加密邢笙。
簡單點說就是一種固定格式的字符串,通常是加密的侍匙;
它由三部分組成氮惯,頭部、載荷與簽名想暗,這三個部分都是json格式妇汗。
- Header 頭部:JSON方式描述JWT基本信息,如類型和簽名算法说莫。使用Base64編碼為字符串
- Payload 載荷: JSON方式描述JWT信息杨箭,除了標(biāo)準(zhǔn)定義的,還可以添加自定義的信息储狭。同樣使用Base64編碼為字符串互婿。
- iss: 簽發(fā)者
- sub: 用戶
- aud: 接收方
- exp(expires): unix時間戳描述的過期時間
- iat(issued at): unix時間戳描述的簽發(fā)時間
- Signature 簽名: 將前兩個字符串用 . 連接后,使用頭部定義的加密算法辽狈,利用密鑰進(jìn)行簽名慈参,并將簽名信息附在最后。
JWT可以使用對稱的加密密鑰刮萌,但更安全的是使用非對稱的密鑰驮配,本篇文章使用的是對稱加密。
代碼修改
數(shù)據(jù)庫
原來使用access_token的時候我們建立了7張oauth2相關(guān)的數(shù)據(jù)表
使用jwt的話只需要在數(shù)據(jù)庫存儲一下client信息即可着茸,所以我們只需要保留數(shù)據(jù)表oauth_client_details壮锻。
其他數(shù)據(jù)表已不再需要,大家可以刪除涮阔。
認(rèn)證服務(wù) AuthorizationServerConfig
- 修改
AuthorizationServerConfig
中TokenStore的相關(guān)配置
@Bean
public TokenStore tokenStore() {
//return new JdbcTokenStore(dataSource);
return new JwtTokenStore(jwtTokenEnhancer());
}
/**
* JwtAccessTokenConverter
* TokenEnhancer的子類猜绣,幫助程序在JWT編碼的令牌值和OAuth身份驗證信息之間進(jìn)行轉(zhuǎn)換。
*/
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 設(shè)置對稱簽名
converter.setSigningKey("javadaily");
return converter;
}
之前我們是將access_token存入數(shù)據(jù)庫敬特,使用jwt后不再需要存入數(shù)據(jù)庫途事,所以我們需要修改存儲方式验懊。
jwt需要使用加密算法對信息簽名,這里我們先使用 對稱秘鑰 (javadaily)來簽署我們的令牌尸变,對稱秘鑰當(dāng)然這也以為著資源服務(wù)器也需要使用相同的秘鑰义图。
- 修改
configure(AuthorizationServerEndpointsConfigurer endpoints)
方法,配置jwt
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//如果需要使用refresh_token模式則需要注入userDetailService
endpoints.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailService)
.tokenStore(tokenStore())
.accessTokenConverter(jwtTokenEnhancer());
}
這里主要是注入accessTokenConverter召烂,即上面配置的token轉(zhuǎn)換器碱工。
經(jīng)過上面的配置,認(rèn)證服務(wù)器已經(jīng)可以幫我們生成jwt token了奏夫,這里我們先使用Postman調(diào)用一下怕篷,看看生成的jwt token。
從上圖看出已經(jīng)正常生成jwt token酗昼,我們可以將生成的jwt token拿到https://jwt.io/網(wǎng)站上進(jìn)行解析廊谓。
如果大家對生成jwt token的邏輯不是很了解,可以在
DefaultTokenServices#createAccessToken(OAuth2Authentication authentication)
和JwtAccessTokenConverter#enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)
上打個斷點麻削,觀察代碼執(zhí)行的效果蒸痹。
刪除認(rèn)證服務(wù)器提供給資源服務(wù)器獲取用戶信息的接口
/**
* 獲取授權(quán)的用戶信息
* @param principal 當(dāng)前用戶
* @return 授權(quán)信息
*/@GetMapping("current/get")
public Principal user(Principal principal){
return principal;
}
用了透明令牌jwt token后資源服務(wù)器可以直接解析驗證token,不再需要調(diào)用認(rèn)證服務(wù)器接口呛哟,所以此處可以直接刪除叠荠。
-
修改jwt token有效期(可選)
jwt token的默認(rèn)有效期為12小時,refresh token的有效期為30天扫责,如果要修改默認(rèn)時間可以注入
DefaultTokenServices
并修改有效時間榛鼎。
@Primary
@Bean
public DefaultTokenServices tokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenEnhancer(jwtTokenEnhancer());
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
//設(shè)置token有效期,默認(rèn)12小時鳖孤,此處修改為6小時 21600
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 6);
//設(shè)置refresh_token的有效期者娱,默認(rèn)30天,此處修改為7天
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
return tokenServices;
}
然后在 configure()
方法中添加tokenServices
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//如果需要使用refresh_token模式則需要注入userDetailService
endpoints.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailService)
//注入自定義的tokenservice苏揣,如果不使用自定義的tokenService那么就需要將tokenServce里的配置移到這里
.tokenServices(tokenServices());
}
資源服務(wù)器 ResourceServerConfig
- 刪除資源服務(wù)器中配置認(rèn)證服務(wù)器的接口屬性
user-info-uri
security:
oauth2:
resource:
id: account-service
- 注入TokenStore 和 JwtAccessTokenConverter
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer(){
JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();
jwtTokenEnhancer.setSigningKey("javadaily");
return jwtTokenEnhancer;
}
注意:對稱加密算法需要跟認(rèn)證服務(wù)器秘鑰保持一致肺然,當(dāng)然這里可以提取到配置文件中。
- 添加
configure(ResourceServerSecurityConfigurer resources)
方法腿准,加入token相關(guān)配置
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
.tokenStore(tokenStore());
}
思考題:資源服務(wù)器使用jwt后從哪校驗token呢?
給應(yīng)用添加 @EnableResourceServer
注解后會給Spring Security的FilterChan添加一個 OAuth2AuthenticationProcessingFilter
拾碌,OAuth2AuthenticationProcessingFilter
會使用 OAuth2AuthenticationManager
來驗證token吐葱。
校驗邏輯主體代碼執(zhí)行順序如下:
建議大家在 OAuth2AuthenticationProcessingFilter#doFilter()
處打個斷點體會一下校驗過程。
網(wǎng)關(guān) SecurityConfig
-
創(chuàng)建
ReactiveJwtAuthenticationManager
從tokenStore加載 OAuth2AccessToken由于原來的access_token是存儲在數(shù)據(jù)庫中校翔,所以我們編寫了
ReactiveJdbcAuthenticationManager
來從數(shù)據(jù)庫獲取access_token弟跑,現(xiàn)在使用jwt我們也需要定義一個jwt的相關(guān)類ReactiveJwtAuthenticationManager
,代碼跟ReactiveJdbcAuthenticationManager
一樣防症,這里就不再貼出孟辑。 注入TokenStore 和 JwtAccessTokenConverter
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer(){
JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();
jwtTokenEnhancer.setSigningKey("javadaily");
return jwtTokenEnhancer;
}
注意:對稱加密算法需要跟認(rèn)證服務(wù)器秘鑰保持一致哎甲,當(dāng)然這里可以提取到配置文件中。
- 修改
SecurityConfig#SecurityWebFilterChain()
方法饲嗽,替換ReactiveJdbcAuthenticationManager
ReactiveAuthenticationManager tokenAuthenticationManager
= new ReactiveJwtAuthenticationManager(tokenStore());
只需要將tokenStore傳入構(gòu)造器即可炭玫。
測試
大家自行測試。
小結(jié)
使用jwt token 和 access_token 最大的區(qū)別就是資源服務(wù)器不再需要去認(rèn)證服務(wù)器校驗token貌虾,提升了系統(tǒng)整體性能吞加,使用jwt后項目的流程架構(gòu)如下:
本系列文章目前是第19篇,如果大家對之前的文章感興趣可以移步至
http://javadaily.cn/tags/SpringCloud
查看
使用了jwt我們不僅要看到j(luò)wt的優(yōu)點尽狠,也要看到它的缺點衔憨,這樣我們才能根據(jù)實際場景自由選擇,下面是jwt最大的兩個缺點:
- jwt是一次性的袄膏,一旦token被簽發(fā)践图,那么在到期時間之前都是有效的,無法廢棄沉馆。如果你中途修改了用戶權(quán)限需要更新信息那就只能重新簽發(fā)一個jwt码党,但是舊的jwt還是可以正常使用,使用舊的jwt拿到的信息也就是過時的悍及。
- jwt 包含了認(rèn)證信息闽瓢,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限心赶。為了防止盜用扣讼,jwt的有效時間不應(yīng)該設(shè)置過長。