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  如果对你帮助了,给个小星星吧。

  • 相关阅读:
    啥是IOC ?啥是DI ?
    Spring是什么?
    Javaweb实训-宠物医院-社区宠物医院登陆页面
    Javaweb实训-宠物医院-社区宠物医院的页面样式
    Bootstrap基础学习(二)
    Bootstrap基础学习(一)
    常用的几种清除float浮动的方法
    jquery的each遍历方法
    正则总结RegExp
    OpenWrt编译到底脚本
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/12154084.html
Copyright © 2011-2022 走看看