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

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

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

1. 前言

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

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

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

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

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

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

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无效了,导致重复发送刷新请求进入死循环,所以不能这样做)

全新基于springcloud的微商城项目完结啦,欢迎学习

/**
 * 刷新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的思考,具体分析会结合失败的例子来演示

1909b0c24cacc632986ff30f6fcfaf43.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方法是异步调用的所以其执行顺序穿插在其中

932d44a83f98339652de0d6646d88eea.png

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

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

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中报错信息如下

cb6f29baf0c807a0073cb6fd4af3ed23.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就能解决上面的问题,全新基于SpringCloud+Vue的微商城项目完结啦,欢迎学习!

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

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

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

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

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

85ee38dd700fd022304149aca1c11f6f.png

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 应用等)下可以尝试客户端实现的方法分担服务器压力

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

推荐全新学习项目

全新基于springboot+vue+vant的前后端分离的微商城项目,包括手机端微商城项目和后台管理系统,整个电商购物流程已经能流畅支持,涵盖商品浏览、搜索、商品评论、商品规格选择、加入购物车、立即购买、下单、订单支付、后台发货、退货等。功能强大,主流技术栈,非常值得学习。

项目包含2个版本:

  • 基于springboot的单体版本

  • 基于spring cloud alibaba的微服务版本

线上演示:https://www.markerhub.com/vueshop

d46782fedfb5f1308637ff6168aed4fa.jpeg

从文档到视频、接口调试、学习看板等方面,让项目学习更加容易,内容更加沉淀。全套视频教程约44小时,共260期,讲解非常详细细腻。下面详细为大家介绍:

架构与业务

使用主流的技术架构,真正手把手教你从0到1如何搭建项目手脚架、项目架构分析、建表逻辑、业务分析、实现等。

单体版本:springboot 2.7、mybatis plus、rabbitmq、elasticsearch、redis

微服务版本:spring cloud alibaba 2021.0.5.0,nacos、seata、openFeign、sentinel

前端:vue 3.2、element plus、vant ui

95000ac626afc84d3b4b3051ce08a22f.png

更多详情请查看:

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

前端实现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

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

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

oandy0的博客 5939

阿里云百炼平台创建智能体-上传文档

阿里云百炼智能体-上传文档文件

weixin_43930851的博客 1358

SpringBoot无感刷新Token实战指南

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

Sgfcgg的博客 346

JLink版本不兼容?手把手教你解决APM32F003F6P6在Keil中烧写闪退和报错

本文详细解析了APM32F003F6P6在Keil中烧写闪退和报错的解决方案,重点介绍了JLink版本兼容性问题。通过实测不同版本组合,推荐使用JLink 7.20a或6.86b与Keil 5.25的黄金组合,并提供详细的降级驱动和工程配置步骤,帮助开发者高效解决烧写困境。

weixin_33694620的博客 418

Floyd算法在社交网络中的应用:如何找到六度人脉的最短路径?

本文探讨了Floyd算法在社交网络分析中的核心应用,特别是如何利用该算法高效计算任意两人之间的最短路径,从而量化“六度人脉”理论。文章通过构建微型社交网络模型,详细拆解了Floyd算法的动态规划原理与代码实现,并展示了如何从计算出的最短路径距离矩阵中挖掘节点中心性、识别社群等关键洞察,为社交推荐、影响力分析等场景提供技术支撑。

blockchain9miner的博客 1004

SpringBoot 无感刷新 Token

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

chinansa的博客 705

Prometheus常用配置介绍

除了 scrape_configs 外,上部还有 global 和 rule_files 部分, 其中 global 中 是 全 局 参 数 , scrape_interval 指 定 全 局 默 认 的 时 间 间 隔 , evaluation_interval 指定告警规则校验的时间间隔,rule_files 指定了告警规 则文件的位置名称。• annotations(注释):它们被用于存储更长的其他信息,例如警报描述 或者链接,注释值也可以被模板化。• alert:告警的名称。

weixin_42886699的博客 2263

SpringBoot 如何做到无感刷新 token?

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

lxw1844912514的博客 204

ros2 humble cartographer 2D激光雷达建图

Cartographer是一个支持多平台和传感器配置的SLAM的系统。 本文主要讲述在ros2 humble系统上cartographer的安装方法,以及如何配置参数实现自己的ros2 bag包的离线建图。

weixin_43087302的博客 6939

Prompt提示优化工具

在大模型使用过程中,Prompt尤为重要。一个好的Prompt可以解决很多的问题。那么如何撰写一个完美的Prompt?下面汇总了一些Prompt工具,可以辅助日常Prompt的编写。

HJ_blog 3109

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

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

方志朋的博客 149

大模型之Spring AI实战系列(二十六):Spring Boot + DeepSeek 实战指南:构建AI聊天应用

在人工智能技术飞速发展的今天,大型语言模型已经成为了推动技术创新的重要力量。DeepSeek作为国内领先的大型语言模型提供商,其推出的DeepSeek系列模型在多个领域都展现出了卓越的性能。本文将详细介绍如何使用Spring Boot框架集成DeepSeek API,构建一个简单的AI聊天应用。我们将实现两个核心功能:普通文本生成和流式文本生成。

寻道AI,探索AI无限可能! 2265

SpringBoot 无感刷新 Token 全解析

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

m0_73735578的博客 441

SAP 成本分摊逻辑与案例(含具体数据)

摘要: SAP成本分摊通过**分配(Allocation)与分摊(Assessment)**两种方式,将间接成本中心费用按预设规则(如人数、工时、比例等)结转至直接成本对象。分配保留原始成本要素(如电费、工资),适用于需明细追踪的场景;分摊则打包为次级成本要素(如管理费用),简化核算。实操案例涵盖行政费用分配(按人数)、水电车间分摊(按工时)、机修作业分摊(作业成本法)及内部订单结算,系统通过KSV1/KSU1配置循环规则,KSV5/KSU5执行结转。关键点在于选择匹配业务的分摊基础,合理配置次级成本要素,

金牌架构师 554

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

SpringBoot 如何做到无感刷新token?

???? 这是一个或许对你有用的社群???? 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:《项目实战(视频)》:从书中学,往事上“练”《互联网高频面试题》:面朝简历学习,春暖花开《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题《精进 Java 学习指南》:系统学习,互联网主流技术栈《必读 Java 源码专栏》:知其然,知其所以然????这是一个或许...

芋艿V 191

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

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

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

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

m0_72340523的博客 1045

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

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
上一篇: 预测一波,这份Java面试题库即将爆火。。。
下一篇: 吃透这套后端技术面经,offer多了6个!
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值