Spring Cloud微服務安全實戰_5-5_refresh_token

本篇解決一個問題,token有效期

 

token是一個短活的東西,session多是3天,可是token可能就2個小時,此時就會出現一種狀況,session還有效可是token失效了,此時再拿着這個token去調用其餘微服務就會失敗了。java

這就涉及到了OAuth2協議中的Refresh token,刷新令牌。刷新令牌說的是,無論你使用OAuth協議中的四種受權類型中的哪種(密碼模式、受權碼模式、簡化模式、客戶端模式),當token失效的時候,你能夠拿着一個refresh_token去從新獲取一個令牌,而不須要輸入用戶名密碼。這樣用戶拿到一個短生命週期的access_token,和一個長生命週期的refresh_token,當access_token失效的時候,就拿refresh_token去換取一個新的access_token。git

理論上來講,access_token能夠設置一個很長的有效期,可是這樣是不安全的,只要拿到access_token,就能夠訪問你的服務了,因此若是這樣作,風險很高。而refresh_token就不同了,以下圖能夠看到,要想經過refresh_token換取access_token,須要攜帶clientId和clientSecret,認證服務器會校驗他倆,這二者是保存在服務器端的,別人是拿不到的,因此即便別人拿到了refresh_token也是沒用的。github

 刷新令牌在哪?

前面寫了二十來篇文章了,歷來沒見到過refresh_token,這是由於,在客戶端應用的表oauth_client_details   裏面有一個字段 refresh_token_validity,若是不配這個字段,認證服務器就不會發refresh_token ,若是配了這個值,認證服務器就會在發access_token的同時,也發一個refresh_token。web

 

模擬token失效案發現場

清空掉 access_token表(清掉以前的長有效期的token)spring

 

 

 將客戶端應用表的 admin 應用的token有效期設置爲10秒,這樣在登陸成功後,客戶端應用的session和認證服務器的session還未失效的狀況下,token就會失效數據庫

 

 

從新登陸,獲取一個有效期10秒的token,調用訂單微服務,前幾回調用因爲token還沒過時,調用成功了,10秒以後,token失效,調用訂單微服務,返回401json

 

 改造客戶端應用,在調用微服務前,判斷若是token失效,就去刷新token

在客戶端admin裏,調用微服務前,會從session中取出token信息,放在請求頭,此時的token多是已通過期的,在這裏處理刷新令牌安全

改造前,是直接從session中拿出token,放在zuul請求頭的,改造後的 SessionTokenFilter :服務器

package com.nb.security.admin; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; /** * 從session獲取token,統一加到請求頭中去 */ @Component public class SessionTokenFilter extends ZuulFilter { private RestTemplate restTemplate = new RestTemplate(); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); AccessToken accessToken = (AccessToken)request.getSession().getAttribute("token"); if(accessToken != null){ //token值,若是沒過時就用Access_token
            String tokenValue = accessToken.getAccess_token(); //若是token已過時,拿refresh_token換取新的access_token
            if(accessToken.isExpired()){ String oauthServiceUrl = "http://gateway.nb.com:9070/token/oauth/token"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//不是json請求 //網關的appId,appSecret,須要在數據庫oauth_client_details註冊
                headers.setBasicAuth("admin","123456"); MultiValueMap<String,String> params = new LinkedMultiValueMap<>(); params.add("refresh_token",accessToken.getRefresh_token());//受權碼
                params.add("grant_type","refresh_token");//受權類型-刷新令牌
 HttpEntity<MultiValueMap<String,String>> entity = new HttpEntity<>(params,headers); ResponseEntity<AccessToken> newToken = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, AccessToken.class); request.getSession().setAttribute("token",newToken.getBody().init());//調一下init方法,設置過時時間 //token值,若是過時了,就設置成新獲取的token
                tokenValue = newToken.getBody().getAccess_token(); } requestContext.addZuulRequestHeader("Authorization","Bearer "+tokenValue); } return null; } }

AccessToken 類也改造了:session

package com.nb.security.admin; import lombok.Data; import java.time.LocalDateTime; import java.util.Date; /** * access_token * Created by: 李浩洋 on 2020-01-02 **/ @Data public class AccessToken { private String access_token; private String refresh_token; private String token_type; private Long expires_in; //過時時間 秒

    private String scope; private LocalDateTime expireTime; //過時時間 //設置token的失效日期
    public AccessToken init(){ //expires_in -3 秒,在token失效以前就失效
        expireTime = LocalDateTime.now().plusSeconds(expires_in -3); return this; } //令牌是否過時
    public boolean isExpired(){ return expireTime.isBefore(LocalDateTime.now()); } }

客戶端應用表裏,添加刷新令牌受權類型

 

 改造認證服務器使其支持refresh_token

 

 重啓四個微服務

 

 從新登陸客戶端應用,連續點擊獲取訂單信息微服務,能夠看到請求,隔10秒就有一個請求時間比較長的,這就是token失效後,又去認證服務器刷新令牌的請求耗時多。

 

 認證服務器日誌也有token失效記錄

 

token表裏也有的refresh_token

 

 

本節github  :https://github.com/lhy1234/springcloud-security/tree/chapt-5-5-refreshtoken  若是對你幫助了,給個小星星吧。

相關文章
相關標籤/搜索