zoukankan      html  css  js  c++  java
  • 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  如果对你帮助了,给个小星星吧。

  • 相关阅读:
    每天一道LeetCode--141.Linked List Cycle(链表环问题)
    每天一道LeetCode--119.Pascal's Triangle II(杨辉三角)
    每天一道LeetCode--118. Pascal's Triangle(杨辉三角)
    CF1277D Let's Play the Words?
    CF1281B Azamon Web Services
    CF1197D Yet Another Subarray Problem
    CF1237D Balanced Playlist
    CF1239A Ivan the Fool and the Probability Theory
    CF1223D Sequence Sorting
    CF1228D Complete Tripartite
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/12154084.html
Copyright © 2011-2022 走看看