SpringBoot 如何做到无感刷新token?

关于实现token无感刷新的解决方案 问题引入 在开发中为了安全或满足分布式场景,通常会舍弃原有的session认证手段,而采用jwt(json web token);但是使用token难免遇到token有效期的问题,如果token长期有效,服务端不断发布新的token,导致有效的token越来越多,这必然是存在安全问题的。而token不想session一样,在用户操作时会进行刷新,为了用户体验,这个刷新就需要自己实现。 方案 一、使用旧token获取新token 如果采取单个token的方式要实现token的自动刷新,就必须使用定时器,每隔一 阅读详情

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

42f8f1671bc118256d1e4635a191d570.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:blog.youkuaiyun.com/PleaseBeStrong
/article/details/138967393

466233fcbf7e2cdf1716176a390e797a.jpeg


1. 前言

最近在搞一个鉴权认证服务器,其中有个问题就是token的无感刷新。Token无感刷新是一种在用户不感知的情况下自动更新访问令牌(Token)的机制,以维持用户的登录状态。

一般是使用一个短期的token来做权限认证,而更长时间的refreshToken来做短token的刷新,而在实现的过程中就有各种问题出来比如:

  • Q1: 是要在服务器端实现还是能在客户端实现?

  • Q2: token过期后无法解析,怎么获取到其中的过期时间?

  • Q3: 无感刷新即是需要在获取到新token后重发原来的request请求,并将二次请求的结果返回给原调用者,如何实现?

下面我就对上面这些问题给出我自己的拙见,希望能对读者有所帮助😁

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2. 客户端实现

2.1 初始版本

想法:每次客户端发起的请求会被服务器端gateway拦截,此时在gateway中判断token是否无效(过期):

  • 过期则返回一个特定的状态码(可以自定义也可以用HTTPStatus)告诉客户端当前token失效

  • 没过期则放行,继续原本的业务逻辑

而前端处可以拦截到当前服务器返回的响应状态码,根据状态码来执行对应的操作,也就是下面要引出的axios

2.1.1 服务器端gateway实现拦截器

注意环境springboot3+java17,通过继承GlobalFilter来实现对应的filter逻辑

@Component
public class MyAccessFilter implements GlobalFilter, Ordered
{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        HttpMethod method = request.getMethod();

        // OPTION直接放行
        if(method.matches(HttpMethod.OPTIONS.name()))
            return chain.filter(exchange);

        //登录请求直接放行
        if(SecurityAccessConstant.REQUEST_LOGGING_URI.equals(uri) && method.matches(HttpMethod.POST.name()))
            return chain.filter(exchange);
   
  //获取token
        String token = JWTHelper.getToken(request.getHeaders().getFirst(SecurityAccessConstant.HEADER_NAME_TOKEN));
        if(null != token){

            //判断token是否过时
            if(!JWTHelper.isOutDate(token)){
                return chain.filter(exchange);

            }else{
                if(!SecurityAccessConstant.REQUEST_REFRESH.equals(uri))    //当前不是刷新请求可以刷新返回的状态码就是511
                    return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getCode(),
                        ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getMessage()));

                //当前是刷新请求 但refreshToken都过期了,即刷新不支持
                return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
            }

        }
        
        return ResponseUtils.out(exchange , ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
    }

    @Override
    public int getOrder() {
        //数值越小 优先级越高
        return Ordered.LOWEST_PRECEDENCE;
    }
}
2.1.1.1 问题Q2解决

正常情况下解析的token会报错,那么就在解析的时候拦截错误,如果catch 到JwtException,此时就认为该token无效已经过期了返回true

否则则执行正常逻辑获取并返回token中的过期时间与当前时间比较的结果

//判断当前token是否过期
public static boolean isOutDate(String token){
    try {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Date expirationDate = claimsJws.getBody().getExpiration();
        return expirationDate.before(new Date());
    } catch (JwtException e) {
        // JWT token无效或已损坏
        return true;
    }
}
2.1.2 axios拦截器

在拦截器中,我们使用判断响应码,如果是401则清空用户数据回退到登录页面,而如果是511则使用refreshToken再请求刷新一次(其他的情况在这里就不做分析,感兴趣的读者可以自行研究)

// 响应拦截器
service.interceptors.response.use(
 
  // 响应成功进入第1个函数
  // 该函数的参数是响应对象
  function(response) {
    console.log(response)
    return response.data.data;
  },
  // 响应失败进入第2个函数,该函数的参数是错误对象
  async function(error) {
    // 如果响应码是 401 ,则请求获取新的 token
    // 响应拦截器中的 error 就是那个响应的错误对象
    if(error.response == undefined)
      return Promise.reject(error);

    const status = error.response.status
    const authStore = useAuthStore()
    let message = ''

    switch(status){
      case 401: // 无权限
        authStore.reset() // 清空store中的权限数据
        window.sessionStorage.removeItem('isAuthenticated')
        window.sessionStorage.removeItem('token')
        window.sessionStorage.removeItem('refreshToken')
        message = 'token 失效,请重新登录'
        // 跳转到登录页  
        window.location.href = '/auth/login';
        break;
      case 511: // 当前token需要刷新
        try {
          const data = refresh()

          if(data !== null){
            data.then((value) => {
              // Use the string value here
              if(value !== ''){
                // 如果获取成功,则把新的 token 更新到容器中
                console.log("刷新 token  成功", value);
                window.sessionStorage.setItem("token",value)
                // 把之前失败的用户请求继续发出去
                // config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
                // return 把 request 的请求结果继续返回给发请求的具体位置
                error.config.headers['Authorization'] = 'Bearer ' +value;
                return service(error.config);
              }
              console.log(value);
            }).catch((error) => {
              // Handle any errors that occurred while resolving the promise
              console.error(error);
            });
            
          }
          
        } catch (err) {
          // 如果获取失败,直接跳转 登录页
          console.log("请求刷线 token 失败", err);
          router.push("/login");
        }
        break;
        case '403':
          message = '拒绝访问'
          break;
        case '404':
          message = '请求地址错误'
          break;
        case '500':
          message = '服务器故障'
          break;
        default:
          message = '网络连接故障'  
   
    }
    
    Message.error(message)
    return Promise.reject(error);
  }
);
2.1.3 refresh刷新token方法实现

这里实现是重新用axios原生发异步请求,而不是使用在request.ts 中导出的请求方法(因为里面定义了请求拦截器,每次请求之前都会取出token并放到请求头,这就又变成请求头中携带的token无效了,导致重复发送刷新请求进入死循环,所以不能这样做)

/**
 * 刷新token 
 * 成功返回新token
 * 失败返回空字符串''
 */
export async function refresh() : Promise<string>{
  const refreshToken = window.sessionStorage.getItem("refreshToken")
  console.log("in >>> " ,refreshToken)
  if(refreshToken == undefined)
      return '' //本来就没有这个更新token则直接返回
  
  try {
    const response = await axios({
      method: 'GET',
      url: 'http://127.0.0.1:9001/api/simple/cloud/access/refresh',// 认证服务器地址
      headers: {
        Authorization: `Bearer ${refreshToken}`, //header中放入的是refreshToken用于刷新请求
      },
    });
  // 如果顺利返回会得到 data,由于后端使用统一结果返回ResultData,所以会多封装一层code、data
    if (response.data) {
      return response.data.data; //所以这里有两个data
    } else {
      return '';
    }
  } catch (error) {
    console.log(error);
    return '';
  }
}
2.1.4 正常和刷新情况下的console输出信息分析

细心的读者可以注意到上边的代码有很多地方有控制台的输出,加上这些可以更方便的读懂代码的逻辑,下面我们就运行代码跑跑看看结果返回情况,这里建议各位结合代码分析看看我做输出的地方是在哪里。

下图是正常情况下的返回结果,注意这里的token是以hizFIGg结尾,而refreshToken是以suvm-EgQ结尾(这两个注意与异常的来比对)正常情况下返回的结果肯定是200即ok

“

注意>>>>>处输出的结果是点击该按钮后点击事件返回的结果,对应着Q3的思考,具体分析会结合失败的例子来演示

81777f819c4f9da304bde53558e13b93.png

下面来看异常情况的分析,由于token太长了,所以拆分两张图片更容易看一点,从左边的图开始分析

  • 在发起第一次请求后,后端gateway拦截器报错 511 (是不是就是对应上面case 511 此时应该用refresh token刷新)

  • in ?>> 进来refresh方法的逻辑,成功打印出refreshToken 以suvm-EgQ结尾(是不是跟上面refreshToken相同)

  • 紧接着就是 输出 刷新token成功 此时返回的是刷新后的token,将其覆盖新的token并重新发送请求

到这里左图分析完毕,进入右图的分析(肯定有读者疑惑你这黄色的warn咋不讲)别急这块我会和右图的红色error一起讲解

  • 紧接上面,用新的token发送请求,此时在请求拦截器处捕获到的token是不是就是更新好的 以V0dYcMA结尾,而refreshToken则以suvm-EgQ结尾(得出结论refreshToken用做刷新,但本身并不刷新)

  • 此时捕获到Uncaught error status 511 这不就是我们一开始的报错吗? 其实就是这样的,原来的按钮点击事件调用getAllUser方法已经结束!!! 返回的结果是error 即是这里的511(把左右三个有颜色的块拼起来一起看就懂了)而由于refresh方法是异步调用的所以其执行顺序穿插在其中

e0baa7dec96adce348db3d94508277f7.png

最后返回结果可以看到已经没有上面注意部分提到的>>>>>输出内容,令通过更新好的token发送二次请求得到的结果记作data,此时的data已经不能返回原来的getAllUser方法调用处,因为原来的方法已经结束,通俗点话说就是这样的二次调用结果毫无意义,用户还是需要刷新网页或者二次点击以获取资源

这就是Q3提出的思考,由于异步调用而非阻塞式的调用方式导致原方法提前终止,可以考虑换成阻塞式的调用refresh方式刷新token,但是这样又会导致该次点击的响应变慢,用户体验差(有更好想法的读者可以在评论区一起讨论)

2.2 改进版本

既然异步方法不得行,那能不能换种思路?不要在失败的时候发送,而是提前检查存在本地的token有没有过期,当检查token过期时间小于一个临界点,则异步调用刷新token方法,更新现有的token信息,此时是不是就解决上面的问题,只要是服务器端gateway拦截到token失效的请求我都要求重新登录。此时就引出一个定时器的概念

“

在TypeScript中,定时器主要是指通过setInterval和setTimeout这两个函数来实现的周期性或延时执行代码的功能。

首先,setInterval是一个可以按照指定的时间间隔重复执行某段代码或函数的方法。它接受两个参数:第一个参数是你想要周期性执行的函数或代码块,第二个参数是时间间隔,单位为毫秒。

由于当setInterval被调用时,它会在指定的时间间隔后执行给定的函数或代码块。这个时间间隔是以毫秒为单位的,而且它是从调用setInterval的那一刻开始计算的。这意味着一旦setInterval被调用,定时器就会立即启动,并在每个指定的时间间隔后重复执行。所以该定时器的设定应该放在login方法登录返回结果处

2.2.1 定义定时器类

通过该定时器类,可以实现MyTimer.start 方法调用setInterval 间隔delay 时间步执行,判断当前的token过期时间是否小于我们设置的minCheck , 如果小于则使用refreshToken异步刷新token

import { refresh } from "@/api/system/auth/index"
import { jwtDecode } from "jwt-decode";

export class MyTimer {
  private timerId: any | null = null;
  // delay为重复探查的间隔时间 , minCheck是判断token是否是快过期的
   start(delay: number, minCheck : number): void {
     this.timerId = setInterval(async () => {
       const currentToken = window.sessionStorage.getItem('token');
       console.log("timer++++")
       if (currentToken) {
         // 如果存在token,判断是否过期
         let expirationTime = 0;
         expirationTime = getExpirationTime(currentToken) ; // 假设有一个函数用于获取token的过期时间
         
         const timeRemaining = expirationTime - Date.now();
         if (timeRemaining <= minCheck) {
           // 如果剩余时间小于等于5分钟,则异步发送刷新请求并更新token
           await refresh();
         }
       } else {
         // 如果不存在token,则直接发送刷新请求并更新token
         await refresh();
       }
   }, delay);
   }
 
   stop(): void {
     if (this.timerId !== null) {
       clearInterval(this.timerId);
       this.timerId = null;
     }
   }
 }
// 获取过期时间
 function getExpirationTime(rawToken:string) : number{
   const res = jwtDecode(rawToken)
   
   return res.exp as number
 }
2.2.2 修改Login点击事件

只用看新增的方法,其他的都是一些权限跟token等的存储

import { MyTimer } from "@/utils/tokenMonitor"

const submit = () => {
  if (validate()) {
    login(formData)
      .then((data: UserInfoRes) => {
        if (data) {
          // 在这里添加需要执行的操作
          const token = data.token;

          // 将token存储到authStore中
          const authStore = useAuthStore()
          authStore.setToken(token)
          window.sessionStorage.setItem('token', token)
          window.sessionStorage.setItem('refreshToken', data.refreshToken)
          authStore.setIsAuthenticated(true)
          window.sessionStorage.setItem('isAuthenticated', 'true')
          authStore.setName(data.name)
          authStore.setButtons(data.buttons)
          authStore.setRoles(data.roles)
          authStore.setRouters(data.routers)
  //新增 引入计时器》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
          const clock = new MyTimer();
          clock.start(1000*30,1000*30);

          init({ message: "logged in success", color: 'success' });
          push({ name: 'dashboard' })

        }
      })
      .catch(() => {
        init({ message: "logged in fail , please check carefully!", color: '#FF0000' });
      });

  }else{
    Message.error('error submit!!')
    return false
  }
}
2.2.3 测试

按理来说测试时候应该没有问题,能正确解析token,而实际运行时候却报错,无法正确解析token报错

“

InvalidTokenError: Invalid token specified: invalid json for part #2

而后续换成jwt.verify()使用密钥来解码同样报错,甚至无法加载出页面,console中报错信息如下

32e3e7debd8de2b3fb916fe4a6ccfe31.png

半天这token解析不了就很奇怪了,后面在网上查阅资料的过程中总结出来,由于后端生成的token是通过jjwt这个依赖实现的,对于不同的库底层的编码实现逻辑会有差异导致a库加密生成的token并不能完全被b库的方法来解密

找到了原因,那我们应该如何获取token中的过期时间呢?可以使用与jjwt相同的实现逻辑库来解码该token或者不妨换个思路,从服务器端下发token的时候我就带上这个过期时间,这样就省去了前端解码这个步骤,所以就引出了如下最终实现版本

2.3 最终定时器版本(实现可以直接看这里)

2.3.1 服务器端修改
2.3.1.1 根据token获取其过期时间
// 获取当前token过期时间 这里不判断是否过期因为是通过了过期判断才进来的
public static Date getExpirationDate(String token) {
    if(StringUtil.isBlank(token))
        return null;

    Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
    return claims.getExpiration();
}
2.3.1.2 发放token处携带过期时间
//存放token到请求头中
String[] tokenArray = JWTHelper.createToken(sysUser.getId(), sysUser.getEmail(), permsList);
map.put("token",tokenArray[0]);
// 新增设置过期时间 毫秒数
map.put("tokenExpire",JWTHelper.getExpirationDate(tokenArray[0]).getTime());
map.put("refreshToken",tokenArray[1]);

同样在refreshToken处也就不是只返回token,也需要带上其过期时间,代码与上面相同就不重复写了

2.3.2 修改监控器类MyTimer

最终版本该类中包含这三个属性,分别是

  • timerId: 定时器的唯一ID

  • delay: 定时器执行的间隔时间

  • minCheck: 判断token过期时间是否小于该值,小于则需执行refresh() 方法来刷新token。

同时使用单例模式全局导出唯一的实例方便管理,对于上面的token无法解析问题,直接从服务器端获取token的过期时间expire然后与当前时间比较就好啦。

import { refresh } from "@/api/system/auth/index"

class MyTimer {
    private timerId: any | null = null;
    private delay: number; //执行间隔时间
    private minCheck: number; //判断token过期时间是否小于该值

    private static instance: MyTimer;

    public static getInstance(): MyTimer {
      if (!MyTimer.instance) {
        MyTimer.instance = new MyTimer();
      }
      return MyTimer.instance;
    }

    private constructor() {
      this.delay = 30000; // Default delay value in milliseconds
      this.minCheck = 60000; // Default minCheck value in milliseconds (1 minutes)
    }
 //启动监控器的方法
    start(): void {
      this.timerId = setInterval(async () => {
        const currentToken = window.sessionStorage.getItem('token');
        console.log("timer++++",currentToken)
        if (currentToken) {
          // 如果存在token,判断是否过期
          const tokenExpireStr = window.sessionStorage.getItem('tokenExpire') as string// 假设有一个函数用于获取token的过期时间
          const expirationTime = parseInt(tokenExpireStr, 10); //以10进制转换string字符串
          const timeRemaining = expirationTime - Date.now();
          console.log("ttime sub++++",timeRemaining)
          if (timeRemaining <= this.minCheck) {
            // 如果剩余时间小于等于minCheck分钟,则异步发送刷新请求并更新token
            try{
              await refresh();
            }catch (error) {
              console.error('刷新失败:', error);
              window.sessionStorage.removeItem('isAuthenticated')
              window.sessionStorage.removeItem('token')
              window.sessionStorage.removeItem('refreshToken')
              Message.error("token reflesh got some ploblem , please login")
              // 跳转到登录页的代码
              window.location.href = '/auth/login';
            }
          }
        } else {
          Message.error("token invalidate , please login")
          // token不存在 则跳转到登录页  
          window.location.href = '/auth/login';
        }
    }, this.delay);

    console.log(this.timerId)
    }
  //关闭监控器的方法
    stop(): void {
      if (this.timerId !== null) {
        clearInterval(this.timerId);
        this.timerId = null;
      }
    }
 //提供设置监控器的刷新间隔和需要刷新的阈值
    setDelay(delay: number): void {
      this.delay = delay;
    }
  
    setMinCheck(minCheck: number): void {
      this.minCheck = minCheck;
    }
  }
//导出全局唯一的实例方便管理
 export const myFilterInstance = MyTimer.getInstance();
// 加到每一个页面上,当页面刷新时候则重启定时器,防止定时器刷掉
 export function onPageRender(){
    // Stop the current timer if it's running
    myFilterInstance.stop();

    // Start the timer with the updated delay and minCheck values
    myFilterInstance.start();
 }
2.3.3 onPageRender 使用

需要注意最后一个方法onPageRender,由于在测试中发现当通过导航栏访问的页面情况下会导致定时器给kill掉了,无法刷新token,发送新请求的时候就会报错,所以最好的方法是在每个页面上添加onPageRender 方法,该方法也很简单就是重启一下定时器,只要给定时器刷新token就能解决上面的问题

在页面中添加的代码如下:

import { onPageRender } from '@/utils/tokenMonitor'
// 新增一个监听器,在页面渲染时候执行
window.addEventListener('load', () => {
  onPageRender();
});
2.3.4 测试

根据最终的测试结果(下图,读者可以结合代码中输出语句来看)

  • 可以看到红色的框框就是进入监控器输出的内容,每次进入都会比对token的过期时间判断是否小于阈值(刷新完后还会用新的过期时间继续比较)

  • 当小于阈值(这里设置1min = 60000ms)则进入refresh逻辑,这个就是上面讲到的内容,一样样的,这样就保证每次刷新携带的token大概率都是最新的!!!😁到此客户端实现功能已经全部讲完啦

4260856964161fc918384256450b8fc0.png

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

3. 服务器端实现

这种实现方法是在gateway处做拦截判断当前的token是否过期,如果过期则通过WebClient携带refreshToken异步发起请求到认证服务器更新,下面代码实现了发起请求到获取数据的过程,但是没有实现原来请求的再发送(偷个懒,后面再来填坑)

// 向认证服务器发送请求,获取新的token
 Mono<ResultData> newTokenMono = WebClient.create().get()
         .uri(buildUri(SecurityAccessConstant.WEB_REQUEST_TO_AUTH_URL+SecurityAccessConstant.REQUEST_REFRESH
                 , new String[]{"refreshToken", token}))
         .retrieve()
         .bodyToMono(ResultData.class);


 // 原子操作
 AtomicBoolean isPass = new AtomicBoolean(false);
 //订阅数据
 newTokenMono.subscribe(resultData -> {
     if(resultData.getCode() == "200"){
         exchange.getRequest().getHeaders().set(SecurityAccessConstant.HEADER_NAME_TOKEN,
                 SecurityAccessConstant.TOKEN_PREFIX + resultData.getData());
         isPass.set(true);
     }

 }).dispose(); // 销毁资源

 if(isPass.get()){
     // 如果成功获取到资源(新token则发送新请求)
     return chain.filter(exchange.mutate().request().build());
 }

4. 怎么选择

在服务器端实现的好处如下:
  • 安全性: 在服务器端进行token刷新可以更好地控制和保护token的安全性,避免将敏感信息暴露给客户端

  • 减少客户端逻辑: 客户端无需过多关注token刷新逻辑,降低了客户端的复杂性和维护成本。

  • 集中管理: 所有用户的token刷新逻辑集中在服务器端,方便统一管理和调整。

  • 解决一致性问题: 用户端刷新token可能导致不同客户端之间的状态不一致,比如一个设备刷新了token而另一个设备未刷新,可能会出现异常情况。

而在客户端实现的好处又如下:
  • 即时性: 客户端自动监控可以实现实时监测token的有效性,并及时触发刷新,确保用户操作的流畅性和体验。

  • 离线支持: 对于需要离线访问或长时间不与服务器通信的应用场景,客户端自动监控可以更好地处理token失效情况。

  • 灵活性: 某些特定场景下,客户端可能更容易实现对token状态的监控和处理,例如需要根据用户行为动态调整token刷新策略等。

  • 减轻服务器压力: 用户端刷新token可以减少服务器负担,尤其对于大量用户同时刷新token时,可分散处理压力。

可见在不同的场景下实现的方法有所不同,要根据实际需求来决定,往往在一些高精度高安全性的系统中适合在服务器端做token的刷新,其他场景(例如移动端应用或简单的 Web 应用等)下可以尝试客户端实现的方法分担服务器压力。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,“长按”或“扫描”下方二维码噢:

44003c0e6be9b86c7eb8e95446f37b53.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

7f3bc0e8ac2cd6747bd486fe8ca6dfbc.png

48566d7c369ca3ff1134e06d80d016c8.png46514a41185d31c9213ed47b114c858b.pngc120ecae51bd751c3d5ce27642bf1821.png6892d1409ff51600f984a1285521d1d5.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
前端实现token的无感刷新#记录 因为服务器的token一版不会设置太长,token过期后就需要重新登录,频繁的登录会造成体验不好的问题,因此,需要体验好的话,就需要定时去刷新token,并替换之前的token。 阅读详情

相关推荐

无感刷新 token

又因为无论是 access token 还是 refresh token 都是放在请求头的 authorization 上携带,此时请求拦截设置的 access token 就会覆盖掉 refresh token,导致刷新接口拿不到 refresh token。前一个刷新请求还没拿到最新的 access token,后一个刷新请求又发出了,这就出现了并发刷新 token ,冗余发送请求的情况。在请求拦截器中保存当前请求的url到数组中,后续的请求都需要判断一下,当前请求的url是否已经在数组中。

阿城的博客 1972

实现无感刷新token(看这一篇就够了)

无感知刷新Token是指,在Token过期之前,系统自动使用Refresh Token获取新的Access Token,从而实现Token的无感知刷新,用户可以无缝继续使用应用。如何判断Token是否过期?如何在Token过期时自动使用Refresh Token获取新的Access Token?下面将介绍如何实现无感知刷新Token的具体步骤。无感刷新Token技术是一种在客户端应用中自动刷新访问令牌的机制,它通过提供用户无感知的刷新操作,改善了用户体验,提高了安全性,同时减轻了服务端的负担。

weixin_57909742的博客 6524

SpringBoot 实现 Token 无感刷新机制

双Token机制实现无感刷新认证,通过AccessToken(短期有效)保障安全性,RefreshToken(长期有效)维持会话连续性。技术实现包括JWT工具类生成/验证Token、拦截器自动刷新Token、Redis存储RefreshToken。前端配合使用请求/响应拦截器处理Token更新,并采用HttpOnly Cookie存储RefreshToken提升安全性。该方案解决了传统单Token在安全性与用户体验间的矛盾,适用于对接口连续性要求高的场景。

wjianwei666的专栏 545

token无感刷新

最近遇到个需求:前端登录后,后端返回和,当token过期时要求用旧token去获取新的token,前端需要做到无痛刷新token,即请求刷新token时要做到用户无感知。

wyh666csdn的博客 3164

3步实现SpringBoot无感刷新Token:再也不怕中途掉线

通过今天的分享,相信你已经掌握了在SpringBoot项目中实现无感刷新Token的方法。无论是简单的登录认证,还是复杂的Token管理,JetCache都能帮你轻松应对。希望这篇文章能够帮助你在未来的开发中更加高效地使用Token认证技术。如果你有任何问题或建议,欢迎在评论区留言,让我们一起交流,共同进步!

java专栏 1137

SpringBoot 实现无感刷新 token

SpringBoot 实现无感刷新 token

qq_30895747的博客 504

SpringBoot无感刷新Token实战指南

无感续约机制虽然能够提升用户体验和安全性,但也存在一些问题和需要思考的地方,如果前端设置的续约阈值过小,可能会导致频繁的续约请求发送到后端,增加了服务器的负载。因此,需要合理设置续约阈值,避免过于频繁地发送续约请求。续约接口可能会被恶意用户利用,因此需要对续约接口进行严格的身份验证和授权,确保只有合法的用户才能调用续约接口。可以考虑使用JWT Token或者OAuth 2.0等机制来确保续约接口的安全性。所以,实现无感续约需要综合考虑安全性、性能和用户体验等方面的因素。

Sgfcgg的博客 346

保姆级教程:用PyBullet和Stable-Baselines3搞定你的第一个机器人强化学习项目

本文提供了一份详细的PyBullet和Stable-Baselines3教程,帮助开发者快速上手机器人强化学习项目。通过环境配置、自定义Gym环境构建、PPO算法参数优化及模型部署的全流程解析,读者将掌握从零开始训练四足机器人 locomotion 任务的完整技术方案。

weixin_26861329的博客 423

Java实战:Spring Boot实现无感刷新Token机制

本文将深入探讨如何在Spring Boot项目中实现无感刷新Token机制,并通过具体的示例代码,逐步引导读者掌握这一核心技术。

oandy0的博客 5939

Windows沙盒神器Sandboxie Plus保姆级教程:从安装到实战测试可疑软件

本文详细介绍了Windows沙盒神器Sandboxie Plus的安装、配置与实战应用,帮助用户安全测试可疑软件。从系统要求、安装步骤到核心功能如多沙盒管理、文件共享和快照恢复,全面解析了这款轻量级沙盒工具的优势与使用技巧,特别适合需要隔离运行未知程序的用户。

weixin_29323049的博客 1157

Visual Studio报错TRK0005找不到CL.exe?三步彻底解决环境变量问题

本文详细解析了Visual Studio报错TRK0005找不到CL.exe的根本原因,并提供了三步彻底解决环境变量问题的方案。从验证安装完整性到配置永久环境变量,再到注册表级修复,帮助开发者建立健壮的开发环境配置体系,确保编译链稳定运行。

weixin_30839881的博客 459

SpringBoot 如何做到无感刷新 token?

不要在失败的时候发送,而是提前检查存在本地的token有没有过期,当检查token过期时间小于一个临界点,则异步调用刷新token方法,更新现有的token信息,此时是不是就解决上面的问题,只要是服务器端gateway拦截到token失效的请求我都要求重新登录。半天这token解析不了就很奇怪了,后面在网上查阅资料的过程中总结出来,由于后端生成的token是通过jjwt这个依赖实现的,对于不同的库底层的编码实现逻辑会有差异导致a库加密生成的token并不能完全被b库的方法来解密。

lxw1844912514的博客 204

从零搭建uniapp+vue3+ts+tailwindcss多端兼容开发环境

本文详细介绍了如何从零搭建uniapp+vue3+ts+tailwindcss多端兼容开发环境,包括环境准备、TailwindCSS集成、小程序兼容性解决方案以及样式优化技巧。通过实战步骤和常见问题解答,帮助开发者快速掌握这套技术栈,提升开发效率。

weixin_28693309的博客 325

SpringBoot 无感刷新 Token

双Token策略:使用两个Token,一个是访问Token(access token),另一个是刷新Token(refresh token)。当访问Token过期时,使用刷新Token向认证服务器请求新的访问Token。通过以上技术点的组合,Spring Boot应用可以在用户无感知的情况下,有效地刷新Token,从而保证用户的持续访问,提升用户体验。例如,在一个Web应用中,用户正在填写一个很长的表单,当Token过期时,如果让用户重新登录,可能会导致用户数据丢失,而无感刷新Token可以避免这种情况。

chinansa的博客 705

Ubuntu开机卡在BusyBox?3种方法快速修复initramfs错误(附详细命令)

本文详细介绍了Ubuntu开机卡在BusyBox并出现initramfs错误的3种修复方法,包括使用fsck检查文件系统、调整BIOS/UEFI存储设置以及修改GRUB内核参数。针对常见的文件系统错误和根设备无法找到问题,提供了具体的命令行操作步骤和实用技巧,帮助用户快速恢复系统启动。

milk8的博客 785

从LLM到VLM再到VLA:三兄弟的“变形记”——定义、架构演进与最新趋同

LLM、VLM、VLA虽然在时间上依次出现,但它们在架构上的演进并非线性替代,而是形成了一个从“理解语言”到“理解世界”再到“改变世界”的认知闭环。VLA之所以能在短短三年内从概念走向现实,正是因为它站在了LLM和VLM的肩膀上——RT-2的泛化能力来自互联网规模的VLM预训练,π0的扩散策略受益于生成式模型的突破,Helix的双系统架构借鉴了“快慢思考”的认知科学洞见。而反过来,VLA在实际部署中遇到的效率瓶颈和实时性要求,也在倒逼LLM和VLM向更轻量、更高效的架构方向演进。

Ven%的博客 495

面试官:Spring Boot 如何做到无感刷新 token?

放一个token,然后前端那边做拦截,拿到请求头里面的新的token,如果这个新的token和老的token不一致,直接将本地的token更换。token的续约偏向于前端的解决方案,即由前端来进行token的过期时间的判断,首先前后端需要对接商量好一个token续约的接口,如果Token即将过期,你在验证用户权限的同时,为用户生成一个新的Token并返回给客户端,客户端需要更新本地存储的Token,还可以做定时任务来刷新Token,可以不生成新的Token,在快过期的时候,直接给Token增加时间。

方志朋的博客 149

微信小程序 - 解决video视频播放黑屏只有声音没有画面问题,使用Video标签无法播放视频失败只有声音、黑屏视频播放不了,安卓手机/苹果ios视频只有声音却没有画面(uniapp微信小程序也适用)

微信小程序video有声音但是画面黑屏,uni-app微信小程序视频播放黑屏但是有音频,微信小程序video android真机或苹果ios系统中黑屏 只有声音没有画面,Uniapp微信小程序 Video MP4视频播放失败,只有声音,黑屏,视频播放不了,解决办法,解决小程序视频编码兼容性问题导致黑屏,出现黑屏但有声音的问题,video标签播放视频有声音,但是黑屏的原因及解决办法,mp4视频编码的问题,video标签播放视频有声音,但是黑屏。uniapp使用video标签无法播放视频出现黑屏问题处理,如何解

🏆 前端领域优质创作者 7784

SpringBoot 无感刷新 Token 全解析

在现代 Web 系统中,用户体验与安全性的平衡是后端开发的核心命题。本文将基于实际业务场景,全面剖析如何借助 Spring Boot 实现“用户在线不中断,身份自动续签”的无感刷新 Token 机制,并结合前后端联动,构建完整的 Token 生命周期管理方案。实现无感刷新 Token,是用户体验与安全性协同优化的重要实践。用户长时间填写表单,没有发出任何请求,点击提交时发现 token 已失效,被重定向到登录页,数据全丢。前端拦截响应头,若发现新的 Token,与本地不一致则自动更新本地 Token。

m0_73735578的博客 442

YOLOv11改进-卷积-引入Upsampling by Dynamic DySample 解决传统上采样灵活性问题

现有的上采样方法如等,依赖于动态卷积来生成上采样的内容感知核,但这些方法需要额外的子网络生成动态卷积核计算量较大。尤其是 FADE 和 SAPA 需要高分辨率的特征图作为引导,进一步增加了计算负担,并限制了应用场景。DySample的核心思想是将上采样过程重新定义为点采样(Point Sampling)。通过学习输入特征图中的采样点坐标,DySample 生成内容感知的采样点来对特征图进行重新采样,而不是使用动态卷积核。具体实现步骤如下:​​1. 输入特征图:大小为 C×H×W的输入特征图。

qq_64693987的博客 4663

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【SpringBoot】 如何做到无感刷新token?

前后端微服务商城项目,手把手教学!前后端微服务商城项目,手把手教学!前后端微服务商城项目,手把手教学!1. 前言最近在搞一个鉴权认证服务器,其中有个问题就是token的无感刷新。Token无感刷新是一种在用户不感知的情况下自动更新访问令牌(Token)的机制,以维持用户的登录状态。一般是使用一个短期的token来做权限认证,而更长时间的refreshToken来做短token的刷新,而在实现的过程...

java思维导图 361

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

SpringBoot无感刷新token的90%人踩坑真相与终极方案

你是不是也以为在响应头塞个X-Access-Token就能实现“无感刷新”?别骗自己了,浏览器根本不会自动用!90%的团队踩过的坑,我全替你踩过——Filter、Interceptor、前端手动监听,全是伪方案。本文撕开“服务端自嗨式刷新”的遮羞布,直击核心:真正的无感,是让token悄悄藏进业务响应体里,前端不感知、不改代码、不写拦截器,顺手就更新了。附赠防刷新风暴的Redis锁方案 + ThreadLocal线程上下文传递秘技,代码直接抄,上线不翻车。别再让前端骂你系统有bug了,这次,我们让刷新,像呼

m0_72340523的博客 1045

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Spring Boot实现无感刷新Token机制

然而,JWT默认的有效期有限,如何在不影响用户体验的前提下实现Token的自动刷新,即所谓的“无感刷新Token”,成为了许多开发者关注的问题。刷新流程 当Access Token即将过期时,客户端携带Refresh Token向服务器请求新的Access Token,服务器验证Refresh Token有效后,颁发新的Access Token,并可以选择性地更新Refresh Token。生成与解析JWT 首先,我们需要引入JWT相关的依赖,如jjwt,并实现JWT的生成与解析。

Maisucsdn的博客 289

【SpringBoot】最佳实践——JWT结合Redis实现双Token无感刷新

JWT是全称是JSON WEB TOKEN,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密,可使用RSA或ECDSA进行公钥/私钥签名。JWT最常见的使用场景就是缓存当前用户登录信息,当用户登录成功之后,拿到JWT,之后用户的每一个请求在请求头携带上字段来辨别区分请求的用户信息。且不需要额外的资源开销。

k123456kah的博客 2828
上一篇: MQ消息积压,把公司系统整挂了!
下一篇: 取代前后端岗,国内又一新兴岗位在崛起!这才是程序员未来5年最好的就业方向!...
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值