地址
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();