logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
Vue框架19
UniApp15
Java编程8
Java基础知识14
SpringBoot35
SpringMVC15
MyBatis9
Spring Cloud Netflix8
Spring Cloud Alibaba7
RocketMQ1
MyCat1
数据库4
MySQL12
Redis8
MongoDB10
H21
Java技术栈1
分布式锁的实现方案
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
Linux常用命令Jar启动脚本VirtualBox安装CentOSVirtualBox安装Ubuntu树莓派安装及使用frp内网穿透ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Niri
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp
创意设计2
Blender:入门知识UI设计基础知识

地址

https://sa-token.cc/index.html

使用实践

添加依赖

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-spring-boot3-starter</artifactId>
	<version>1.45.0</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
	<groupId>cn.dev33</groupId>
	<artifactId>sa-token-redis-template</artifactId>
	<version>1.45.0</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
</dependency>

yml配置

# Sa-Token 配置 (文档: https://sa-token.cc)
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: saToken
  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
  timeout: 2592000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
  active-timeout: -1
  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
  is-share: true
  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
  token-style: simple-uuid
  # 是否输出操作日志
  is-log: true
  # 日志等级(trace、debug、info、warn、error、fatal),此值与 logLevelInt 联动
  logLevel: trace
  # 是否尝试从 请求体 里读取 Token
  isReadBody: false
  # 是否尝试从 header 里读取 Token
  isReadHeader: true
  # 是否尝试从 cookie 里读取 Token,此值为 false 后,StpUtil.login(id) 登录时也不会再往前端注入Cookie
  isReadCookie: false
  # 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
  isLastingCookie: false
  # 是否在登录后将 Token 写入到响应头
  isWriteHeader: false
  # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
  autoRenew: true

配置类

SaTokenConfigure

package com.project.config.saToken;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

/**
 * Sa-Token 配置类
 */
@Configuration
public class SaTokenConfigure {

    @PostConstruct
    public void init() {
        // 先配特殊规则(注意顺序,匹配到就执行,不再往下匹配)
        //SaRouter.match("/report/export", r -> {
        //    // 特殊权限码
        //    StpUtil.checkPermission("report:download");
        //});

        // 再配全局规则
        SaRouter.match("/**")
                .notMatch("/login", "/captcha", "/getEncrypt")
                .check(r -> {
                    String path = SaHolder.getRequest().getRequestPath();
                    String perm = convertPathToPerm(path);
                    StpUtil.checkPermission(perm);
                });
    }

    /**
     * 路径转权限码的自定义规则
     */
    private String convertPathToPerm(String path) {
        // 特殊映射:所有 /xxx/editInfo 都映射到 xxx:edit
        if (path.matches("^/\\w+/editInfo${content}quot;)) {
            // sysUser, order, product ...
            String module = path.split("/")[1];
            return module + ":edit";
        }
        // 常规映射
        String perm = path.substring(1).replace("/", ":");
        return perm.toLowerCase();
    }

}

SaTokenException

package com.project.config.saToken;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import com.project.vo.ComResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 异常拦截
 */
@Order(1)
@Slf4j
@RestControllerAdvice(basePackages = "com.project")
public class SaTokenException {

    /**
     * 未登录异常
     */
    @ExceptionHandler(value = {NotLoginException.class})
    public ComResult<String> NotLoginException(Exception e) {
        log.error("未登录:{}", e.getMessage());
        return ComResult.notLogin();
    }

    /**
     * 未授权异常
     */
    @ExceptionHandler(value = {NotPermissionException.class})
    public ComResult<String> NotPermissionException(Exception e) {
        log.error("无权访问:{}", e.getMessage());
        return ComResult.unauthorized();
    }

}

SaTokenInterceptor

package com.project.config.saToken;

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

/**
 * Sa-Token 拦截器 + 权限实现
 * StpUtil.login(Object userId);
 * StpUtil.getLoginIdAsLong(); 获取当前会话账号id, 并转化为`long`类型
 * StpUtil.getTokenValue(); 获取当前会话的 token 值
 * StpUtil.logout();
 * 相关注解
 * - @SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法,拦截器已经给所有接口加了
 * - @SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法
 */
@Configuration
public class SaTokenInterceptor implements WebMvcConfigurer {

    /**
     * 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                // 排除登录接口、验证码获取接口、建立加解密会话
                .excludePathPatterns("/login", "/captcha", "/getEncrypt");
    }

}

StpInterfaceImpl

package com.project.config.saToken;

import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 自定义权限加载接口实现类
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        int adminFlag = StpUtil.getSession().getInt("adminFlag");
        List<String> list = new ArrayList<>();
        if (Objects.equals(adminFlag, 1)) {
            // 超级管理员有所有权限(包括多级)匹配所有权限,不区分层级
            list.add("**");
        } else {
            // 非管理员权限,查询数据库 TODO
            list.add("user.add");
            list.add("user.update");
        }
        return list;
    }

    /**
     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        return new ArrayList<>();
    }

}

UserContext

package com.project.config.saToken;

import cn.dev33.satoken.stp.StpUtil;

/**
 * 用户登录信息
 */
public class UserContext {

    private static final String USER_KEY = "currentUser";

    /**
     * 登录时初始化
     */
    public static void setCurrentUser(UserContextVo user) {
        StpUtil.getSession().set(USER_KEY, user);
    }

    /**
     * 获取当前用户(返回强类型)
     */
    public static UserContextVo getCurrentUser() {
        return (UserContextVo) StpUtil.getSession().get(USER_KEY);
    }

}

UserContextVo

package com.project.config.saToken;

import lombok.Data;

/**
 * 用户上下文
 */
@Data
public class UserContextVo {

    /**
     * 用户id
     */
    private Long id;

    /**
     * 用户名
     */
    private String name;

}

登录信息存放

直接存储VO对象

// 登录时
StpUtil.getSession().set("user", user);  // 只存一次

// 使用时
User user = (User) StpUtil.getSession().get("user");
String nickname = user.getNickname();  // 直接从对象取
Long deptId = user.getDeptId();
List<Role> roles = user.getRoles();
地址
使用实践
添加依赖
yml配置
配置类
SaTokenConfigure
SaTokenException
SaTokenInterceptor
StpInterfaceImpl
UserContext
UserContextVo
登录信息存放