当前位置: 首页 > article >正文

springboot+satoken实现刷新token(值变化)

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

🎏:你只管努力,剩下的交给时间

🏠 :小破站

springboot+satoken实现刷新token

    • satoken是什么?支持什么?
    • 为什么需要?
    • token一直不变存在的问题
      • 1. 安全风险增加
      • 2. 难以撤销 Token
      • 3. 权限滥用和过期信息的风险
      • 4. 缺乏会话管理
      • 5. 影响用户隐私
      • 6. 无法确保设备和网络变化
      • 7. 用户体验不佳
    • 逻辑+代码实现
      • 代码实现
    • 拦截器知识补充
      • 1. 注册顺序决定执行顺序
      • 2. 拦截器方法的执行顺序
      • 3. 优先级控制
      • 总结

satoken是什么?支持什么?

satoken官网

借用官网的一句话, Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证单点登录OAuth2.0分布式Session会话微服务网关鉴权 等一系列权限相关问题。
这里我们只说明刷新token,也就是前后端分离的场景中常见的一种方案。

为什么需要?

在satoken中存在两个token,一个是真正的token有效期,还有一个是活跃token,也就是说当活跃token过期的时候是不能访问服务的,需要调用相关方法解除。而当正真的token过期的时候就需要登录重新获取凭证。

所以其实根据上面的表述也是可以实现刷新token的
前端后端约定,当后端因为活跃token过期返回给前端响应的状态值,前端拦截并重新调用相关方法,这样也是可以实现刷新token的。

token一直不变存在的问题

Token 长期不变或过期时间过长会带来一系列安全和用户体验方面的问题。以下是一些主要的风险和潜在问题:

1. 安全风险增加

  • Token 被截获的风险:如果攻击者通过某种方式截获了用户的 Token,那么在 Token 长期不变或过期时间过长的情况下,攻击者可以持续使用该 Token 访问用户的账号,而用户不会意识到。这极大增加了账号被恶意使用的风险。
  • 无法控制 Token 失效:在 Token 长期有效的情况下,即使用户想要主动注销或更改密码,攻击者手中的旧 Token 仍然有效,导致安全威胁无法解除。
  • 无法防御会话劫持:当用户登录后 Token 长期不变,一旦发生会话劫持,攻击者可以一直利用该 Token 冒充用户进行操作,直到 Token 失效。

2. 难以撤销 Token

  • Token 黑名单机制的缺乏:当 Token 的有效期非常长时,后端很难立即撤销某个 Token。即使用户账号被禁用或者注销,旧的 Token 可能依然可以继续访问系统,直到 Token 到期。
  • 缺乏灵活性:Token 如果是长期有效的,即便用户被强制下线,无法简单地让旧 Token 失效,除非重新设计 Token 管理系统。

3. 权限滥用和过期信息的风险

  • 权限变化后无法及时更新:当 Token 长时间有效,而用户的权限发生了变化(例如角色升级、降级或权限被撤销),旧 Token 可能依然具有不该有的权限。这可能导致用户获得不适当的访问权限。
  • 过期数据风险:如果用户 Token 长期不更新,系统可能无法捕捉到最新的用户状态变更,比如权限、角色、信息等,导致系统提供了不正确的访问权限或内容。

4. 缺乏会话管理

  • 无法追踪用户的活跃度:长期有效的 Token 会让系统无法准确跟踪用户的登录会话。系统无法判断用户是否活跃,用户的最后访问时间也无法准确追踪。
  • 无法强制用户重新登录:如果 Token 过期时间过长,用户可能在极长时间内不需要重新登录,丧失了会话管理的能力。对于需要更高安全性的场景,强制用户定期登录是必要的。

5. 影响用户隐私

  • 隐私泄露风险增加:Token 长期有效可能使得用户的个人信息在长时间内暴露于潜在的攻击面,增加了隐私泄露的风险。如果用户长期未使用系统,Token 应该过期以保护用户隐私。
  • 设备共享中的风险:如果用户在共享设备上登录,而 Token 长期不失效,其他人可以轻松访问用户账户,特别是在用户忘记登出或清理浏览器时。

6. 无法确保设备和网络变化

  • IP地址、设备等环境因素没有变化:一些 Token 通常会包含用户设备、IP 地址等信息来防止 Token 被滥用。如果 Token 长期不变,那么即使用户的网络环境发生了变化,系统也无法感知。这将导致 Token 在不可信的环境中继续使用,增加了安全风险。

7. 用户体验不佳

  • 无法提供个性化内容更新:长期不变的 Token 可能导致系统无法捕捉用户状态的实时变化,从而影响个性化内容推荐或提示用户更新信息的能力。
  • 会话管理不灵活:如果用户希望在不同设备上管理会话(如在一个设备上登出时使另一个设备上的 Token 失效),长期不变的 Token 可能无法支持这种场景。

逻辑+代码实现

可以采用双拦截器实现,第一个拦截器是自定义的,而这个拦截器总是返回true,第二个拦截器使用satoken的拦截器做一些登录或者权限的认证。

在这里插入图片描述

代码实现

package fun.acowbo.config;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author <a href="https://acowbo.fun">acowbo</a>
 * @since 2024/8/27
 */
@Slf4j
@Component
public class CustomInterceptor implements HandlerInterceptor{

    @Value("${sa-token.token-name}")
    private String tokenName;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            return true;
        }
        long tokenActivityTimeout = StpUtil.getTokenActivityTimeout();
        log.info("tokenActivityTimeout:{}", tokenActivityTimeout);
        long tokenTimeout = StpUtil.getTokenTimeout();
        if (tokenActivityTimeout > 0){
            response.setHeader(tokenName, StpUtil.getTokenValue());
            response.setHeader("Access-Control-Expose-Headers", tokenName);
        }
        if (tokenActivityTimeout < 0 && tokenTimeout > 0){
            // 首先要让token活跃
            StpUtil.updateLastActivityToNow();
            String loginId = (String) StpUtil.getLoginId();
            // 先退出,否则之前的token还能用
            StpUtil.logout(loginId);
            // 重新设置token,这里仅仅是为了安全,否则始终token是一个值
            StpUtil.login(loginId,new SaLoginModel().setToken(IdUtil.randomUUID()));
            // 请求头修改token的值,否则在第二个拦截器会报错,因为老的token已经失效了
            request.setAttribute(tokenName, StpUtil.getTokenValue());
            // 响应头设置值
            response.setHeader(tokenName, StpUtil.getTokenValue());
            response.setHeader("Access-Control-Expose-Headers", tokenName);
        }
        return true;
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

    }
}

package fun.acowbo.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import java.util.Arrays;

/**
 * description: sa-token权限配置类
 *
 * @author <a href="https://acowbo.fun">acowbo</a>
 * @since 2024/6/7 13:59
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    @Value("${excludePath}")
    private String excludePath;

    @Resource
    private CustomInterceptor customInterceptor;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 允许所有路径
        registry.addMapping("/**")
                // 允许所有来源
                .allowedOrigins("*")
                // 允许的 HTTP 方法
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                // 允许的请求头
                .allowedHeaders("*")
                // 允许发送 Cookie
                .allowCredentials(false)
                // 预检请求的缓存时间
                .maxAge(3600);
    }
    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(customInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(Arrays.asList(excludePath.split(",")));
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns(Arrays.asList(excludePath.split(",")));
    }
}

拦截器知识补充

在 Spring 框架中,如果定义了多个拦截器,它们的执行顺序是根据它们的注册顺序决定的。具体的执行顺序可以通过以下规则来理解:

1. 注册顺序决定执行顺序

在 Spring 中,拦截器是通过 WebMvcConfigurer 接口中的 addInterceptors 方法进行注册的。多个拦截器会按照它们注册的先后顺序进行调用。

  • 拦截器的执行顺序(进入请求时):按照注册顺序依次调用,先注册的拦截器会先执行。
  • 拦截器的执行顺序(响应时):响应返回时,拦截器的执行顺序与请求时相反,最后注册的拦截器会最先执行。

示例

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new SecondInterceptor()).addPathPatterns("/**");
    }
}

在这个例子中:

  • 进入请求时FirstInterceptor -> SecondInterceptor
  • 返回响应时SecondInterceptor -> FirstInterceptor

2. 拦截器方法的执行顺序

拦截器的核心方法有以下三个,它们的调用顺序也需要注意:

  • preHandle: 在请求处理之前执行。
  • postHandle: 在请求处理完后(但在视图渲染之前)执行。
  • afterCompletion: 在视图渲染完成后执行。

当有多个拦截器时:

  • preHandle 方法按照拦截器的注册顺序执行。
  • postHandleafterCompletion 方法则按照相反顺序执行。

3. 优先级控制

如果需要更精确地控制拦截器的顺序,除了按注册顺序,还可以借助其他方法:

  • 排序接口:通过实现 Ordered 接口或使用 @Order 注解,可以为拦截器明确指定顺序。
  • 配置拦截器链的顺序:通过配置文件明确指定拦截器的顺序,也可以使用 InterceptorRegistry 的 API 动态调整顺序。

总结

  • 拦截器的执行顺序取决于它们的注册顺序,先注册的拦截器先处理请求,后注册的拦截器先处理响应。
  • 可以使用 @Order 注解或实现 Ordered 接口来精确控制多个拦截器的执行顺序。

http://www.kler.cn/news/324838.html

相关文章:

  • 威胁检测与防范:如何及时、准确对抗安全风险
  • react-markdown 使用 rehype-katex,解决锚点跳转后渲染异常
  • linux 下mailx 的使用。发送短信
  • 在vue项目中禁用鼠标右键,选中
  • STM32 MCU学习资源
  • excel怎么转换json
  • Linux中gcc,g++常用编译选项
  • composer环境变量(phpstudy集成环境)无法使用问题
  • 【iOS】MVC架构模式
  • Linux系统中命令wc
  • Python:Spoonfed - (2-09) Cinema 4D 选择 (搬砖)
  • macos搭建flutter开发环境 3.24.3版本 2024年9月25日实测部署
  • 【Python】Django Grappelli:打造优雅且现代化的 Django 管理后台
  • win10如何禁止指定程序运行?教你5个方法!抓紧学!码住了!
  • jetlinks物联网平台学习4:http协议设备接入
  • hive如何删除分区
  • Maven-三、聚合
  • 【Python】FeinCMS:轻量级且可扩展的Django内容管理系统
  • 应用性能管理工具-SkyWalking
  • 精通Maven:多模块项目中的依赖管理
  • 支付宝沙箱环境 支付
  • 18.Linux-配置DNF仓库
  • 15分钟学 Python 第29天 : 数据库基础
  • 【Linux】防火墙
  • 《马力欧+疯狂兔子 星耀之愿》风灵月影修改器秘籍:轻松征服星辰大海
  • 数据结构——顺序表(基础代码题)
  • 【chrome 插件】初窥
  • JAVA基础:AtomicInteger
  • 解锁高效工作的秘密武器
  • 足底筋膜炎怎么治疗才能彻底除根