Spring Security OAuth2.0認證受權六:先後端分離下的登陸受權

歷史文章css

Spring Security OAuth2.0認證受權一:框架搭建和認證測試
Spring Security OAuth2.0認證受權二:搭建資源服務
Spring Security OAuth2.0認證受權三:使用JWT令牌
Spring Security OAuth2.0認證受權四:分佈式系統認證受權
Spring Security OAuth2.0認證受權五:用戶信息擴展到jwthtml

本篇文章將會解決上一篇文章《Spring Security OAuth2.0認證受權五:用戶信息擴展到jwt 》中遺留的問題,並在原有的項目中新增模塊business-server用來充當前端頁面的web容器並轉發登陸請求和更換token的請求等,以模擬先後端分離下的登陸以及更換token操做。前端

1、jwt令牌在網關處的過時時間校驗

上一篇文章中講了在網關處解析token並轉發到目標服務的操做,由於使用了jwt令牌的緣由,因此省了一步到認證服務器認證的操做,只要驗籤成功,就認爲令牌有效。這實際上留下了一個bug:服務端沒法主動取消jwt令牌,因此這個令牌只要客戶端保存下來,若是不調用認證服務器的令牌驗證接口,這個jwt令牌將永遠有效。所以須要在網關處加上對過時時間的校驗。java

在TokenFilter中添加如下代碼邏輯git

//取出exp字段,判斷token是否已通過期
try {
    Map<String, Object> map = objectMapper.readValue(payLoad, new TypeReference<Map<String, Object>>() {
    });
    long expiration = ((Integer) map.get("exp")) * 1000L;
    if (expiration < new Date().getTime()) {
        return unAuthorized(exchange, "未認證的請求:token存在,可是已經失效",WrapperResult.TOKEN_EXPIRE);
    }
} catch (IOException e) {
    log.error("", e);
    return unAuthorized(exchange, "未認證的請求:錯誤的token",null);
}

2、refresh-token接口缺乏用戶信息

refresh-token在access_token過時,可是refresh-token未過時的時候使用,目的是使用refresh_token更新已通過期的access_token,這樣理論上來講,客戶端只要能在refresh_token過時以前進行任意操做,就能夠避免從新登陸了。web

上一篇文章中將用戶信息放到了jwt token中並返回給客戶端,可是若是使用refresh_token更新token,後端會報錯,前端取到的token中則缺乏了用戶信息。究其緣由,和JwtAccessTokenConverter有關係,關於這個類的實例,當初建立的方法以下spring

@Bean
public JwtAccessTokenConverter accessTokenConverter(){
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//對稱祕鑰,資源服務器使用該祕鑰來驗證
    return jwtAccessTokenConverter;
}

這裏的new操做省了不少默認參數的指定,且先看下爲啥會缺乏用戶信息,擴展用戶信息的關鍵在於方法com.kdyzm.spring.security.auth.center.service.MyUserDetailsServiceImpl#loadUserByUsername,這裏擴展了用戶信息,使其從單純的username字符串變成了UserDetailsExpand對象,而後在加強方法com.kdyzm.spring.security.auth.center.enhancer.CustomTokenEnhancer#enhance中將擴展信息取出來放到Token中。sql

通過debug,發現後端

2021-01-29_162556.jpg

最終發現是以下代碼的問題org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter#extractAuthentication瀏覽器

public Authentication extractAuthentication(Map<String, ?> map) {
    if (map.containsKey(USERNAME)) {
        Object principal = map.get(USERNAME);
        Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
        //運行到這裏的時候userDetailsService爲空,因此並無執行自定義的loadUserByUsername方法
        if (userDetailsService != null) {
            UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
            authorities = user.getAuthorities();
            principal = user;
        }
        return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
    }
    return null;
}

層層網上追尋調用鏈,居然是JwtAccessTokenConverter建立的時候省略參數致使的,只須要如此作就能夠解決問題了

@Bean
public JwtAccessTokenConverter accessTokenConverter(){
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
    DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
    userTokenConverter.setUserDetailsService(userDetailsService);
    tokenConverter.setUserTokenConverter(userTokenConverter);
    jwtAccessTokenConverter.setAccessTokenConverter(tokenConverter);
    jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//對稱祕鑰,資源服務器使用該祕鑰來驗證
    return jwtAccessTokenConverter;
}

JwtAccessTokenConverter對象建立的時候指定DefaultUserAuthenticationConverter使用的userDetailsService便可。

3、新建business-server模塊做爲web容器

這裏新建的business-server模塊有兩個功能

  1. 充當web容器,該服務並無使用模板化技術,使用的是純html、css實現前端
  2. 轉發前端登陸、更換token請求

可能會有人對第二條有疑問,爲何要這麼作?以前測試的時候基本上都是使用postman發起的請求,請求的方式是這樣的http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123能夠看到這裏傳遞了很重要的參數client_idclient_secret,這兩個參數不管如何也不該當泄露給前端,一般都是中間的真正的客戶端服務拼接這兩個參數再將請求轉發給認證服務

4、先後端分離

設計上想要實現如下功能

  1. 首頁未登陸則提示用戶登陸,已經登陸則展現用戶我的信息
  2. 用戶登陸以後將令牌保存到localStorage
  3. token過時以後用戶能夠選擇使用refresh_token更換已通過期的令牌(access_token)
  4. 已通過期的refresh_token不能用於更換新的令牌

一、關閉認證服務表單登陸

之前請求認證服務的任意接口,若是沒有認證,則都會跳轉到系統自帶的登陸頁面,如今咱們想要實現先後端分離了,原來系統自帶的登陸頁面就有些礙眼了,直接關閉就好。關閉方法以下,spring security的配置更改成以下:

.formLogin()
                .disable();

二、先後端代碼

前端代碼在business-server/src/main/resources/static目錄下,只有兩個頁面,一個首頁,一個登錄頁面

後端只有兩個接口

  • 登陸接口:com.kdyzm.spring.security.oauth.study.business.server.controller.LoginController#login
  • 更新token接口:com.kdyzm.spring.security.oauth.study.business.server.controller.TokenController#refreshToken

其它不作贅述,不過前端頁面寫起來挺麻煩的。。難是不難的

5、測試

源代碼:

測試前首先須要從新執行初始化sql(auth-server/docs/sql/init.sql),而後依次啓動 register-servergateway-serverauth-serverresource-serverbusiness-server 五個服務

啓動成功後打開瀏覽器,輸入http://127.0.0.1:30002/地址,就會看到如下頁面
2021-01-29_171442.jpg
點擊登陸以後,出現登陸框
2021-01-29_171532.jpg
輸入帳號密碼以後,登陸成功以後會跳轉首頁,就會看到我的信息
2021-01-29_171754.jpg
這裏設置的token有效期爲10秒,因此很快token就會失效,十秒鐘以後刷新頁面就會有新的提示
2021-01-29_171858.jpg
接下來能夠有兩種選擇,一種是使用refresh-token更新失效的令牌,另一種是從新登陸,這裏refresh_token的有效期也很短,只有30秒,若是超出30秒,則會更新失敗,提示以下
2021-01-29_172049.jpg
而若是在30秒內刷新令牌,則會從新獲取到令牌並刷新當前頁

6、源代碼地址

源代碼地址:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v7.0.0

個人博客地址:https://blog.kdyzm.cn/

相關文章
相關標籤/搜索