SpringCloud Alibaba微服務(wù)實戰(zhàn)十七 - JWT認(rèn)證

概述

在 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)點就叫去中心化。

image.png

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編碼為字符串互婿。
    1. iss: 簽發(fā)者
    2. sub: 用戶
    3. aud: 接收方
    4. exp(expires): unix時間戳描述的過期時間
    5. iat(issued at): unix時間戳描述的簽發(fā)時間
  • Signature 簽名: 將前兩個字符串用 . 連接后,使用頭部定義的加密算法辽狈,利用密鑰進(jìn)行簽名慈参,并將簽名信息附在最后。

JWT可以使用對稱的加密密鑰刮萌,但更安全的是使用非對稱的密鑰驮配,本篇文章使用的是對稱加密。

代碼修改

數(shù)據(jù)庫

原來使用access_token的時候我們建立了7張oauth2相關(guān)的數(shù)據(jù)表

image.png

使用jwt的話只需要在數(shù)據(jù)庫存儲一下client信息即可着茸,所以我們只需要保留數(shù)據(jù)表oauth_client_details壮锻。


image.png

其他數(shù)據(jù)表已不再需要,大家可以刪除涮阔。

認(rèn)證服務(wù) AuthorizationServerConfig

  1. 修改 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ù)器也需要使用相同的秘鑰义图。

  1. 修改 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。

image.png

從上圖看出已經(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í)行的效果蒸痹。

  1. 刪除認(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ù)器接口呛哟,所以此處可以直接刪除叠荠。

  1. 修改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

  1. 刪除資源服務(wù)器中配置認(rèn)證服務(wù)器的接口屬性 user-info-uri
security:  
    oauth2:  
        resource:  
            id: account-service
  1. 注入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)然這里可以提取到配置文件中。

  1. 添加 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í)行順序如下:

image.png

建議大家在 OAuth2AuthenticationProcessingFilter#doFilter() 處打個斷點體會一下校驗過程。

網(wǎng)關(guān) SecurityConfig

  1. 創(chuàng)建 ReactiveJwtAuthenticationManager 從tokenStore加載 OAuth2AccessToken

    由于原來的access_token是存儲在數(shù)據(jù)庫中校翔,所以我們編寫了 ReactiveJdbcAuthenticationManager 來從數(shù)據(jù)庫獲取access_token弟跑,現(xiàn)在使用jwt我們也需要定義一個jwt的相關(guān)類 ReactiveJwtAuthenticationManager,代碼跟 ReactiveJdbcAuthenticationManager 一樣防症,這里就不再貼出孟辑。

  2. 注入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)然這里可以提取到配置文件中。

  1. 修改 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)如下:

image.png

本系列文章目前是第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è)置過長。
image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缨叫,一起剝皮案震驚了整個濱河市椭符,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耻姥,老刑警劉巖销钝,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異琐簇,居然都是意外死亡蒸健,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門婉商,熙熙樓的掌柜王于貴愁眉苦臉地迎上來似忧,“玉大人,你說我怎么就攤上這事丈秩《疲” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵蘑秽,是天一觀的道長饺著。 經(jīng)常有香客問我箫攀,道長,這世上最難降的妖魔是什么幼衰? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任靴跛,我火速辦了婚禮,結(jié)果婚禮上塑顺,老公的妹妹穿的比我還像新娘汤求。我一直安慰自己,他們只是感情好严拒,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布扬绪。 她就那樣靜靜地躺著,像睡著了一般裤唠。 火紅的嫁衣襯著肌膚如雪挤牛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天种蘸,我揣著相機與錄音墓赴,去河邊找鬼。 笑死航瞭,一個胖子當(dāng)著我的面吹牛诫硕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刊侯,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼章办,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滨彻?” 一聲冷哼從身側(cè)響起藕届,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亭饵,沒想到半個月后休偶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辜羊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年踏兜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片八秃。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡碱妆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喜德,到底是詐尸還是另有隱情,我是刑警寧澤垮媒,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布舍悯,位于F島的核電站航棱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏萌衬。R本人自食惡果不足惜饮醇,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秕豫。 院中可真熱鬧朴艰,春花似錦、人聲如沸混移。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽歌径。三九已至毁嗦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間回铛,已是汗流浹背狗准。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茵肃,地道東北人腔长。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像验残,于是被迫代替她去往敵國和親捞附。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355