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


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

 

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

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

理論上來說,access_token可以設置一個很長的有效期,但是這樣是不安全的,只要拿到access_token,就可以訪問你的服務了,所以如果這樣做,風險很高。而refresh_token就不一樣了,如下圖可以看到,要想通過refresh_token換取access_token,需要攜帶clientId和clientSecret,認證服務器會校驗他倆,這兩者是保存在服務器端的,別人是拿不到的,所以即使別人拿到了refresh_token也是沒用的。

 刷新令牌在哪?

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

 

模擬token失效案發現場

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

 

 

 將客戶端應用表的 admin 應用的token有效期設置為10秒,這樣在登錄成功后,客戶端應用的session和認證服務器的session還未失效的情況下,token就會失效

 

 

重新登錄,獲取一個有效期10秒的token,調用訂單微服務,前幾次調用由於token還沒過期,調用成功了,10秒之后,token失效,調用訂單微服務,返回401

 

 改造客戶端應用,在調用微服務前,判斷如果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 類也改造了:

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  如果對你幫助了,給個小星星吧。

 

 

歡迎關注個人公眾號一起交流學習:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM