Sa-Token 使用指南
本文档详细介绍 Sa-Token 权限认证框架的使用方法。
简介
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决登录认证、权限验证、Session 会话、单点登录等问题。
核心特性
- 简单易用:API 设计简洁,上手快
- 功能强大:支持多种认证模式
- 高性能:轻量级设计,性能优异
- 灵活扩展:支持自定义扩展
基础配置
Maven 依赖
xml
<dependencies>
<!-- Sa-Token 核心 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Sa-Token 整合 Redis -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.38.0</version>
</dependency>
</dependencies>配置文件
yaml
sa-token:
# Token 名称
token-name: Authorization
# Token 有效期(秒)-1 代表永不过期
timeout: 2592000
# 是否允许同一账号多地同时登录
is-concurrent: true
# Token 风格
token-style: uuid
# 是否从请求头中读取 Token
is-read-header: true
# Token 前缀
token-prefix: Bearer配置拦截器
java
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
SaRouter.match("/**")
.notMatch("/auth/login")
.notMatch("/auth/register")
.check(r -> StpUtil.checkLogin());
})).addPathPatterns("/**");
}
}登录认证
登录接口
java
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public R<LoginVO> login(@RequestBody LoginDTO dto) {
// 验证用户名密码
User user = authService.validateUser(dto.getUsername(), dto.getPassword());
if (user == null) {
return R.fail("用户名或密码错误");
}
// 登录成功,生成 Token
StpUtil.login(user.getId());
// 获取 Token
String token = StpUtil.getTokenValue();
// 设置 Session 数据
SaSession session = StpUtil.getSession();
session.set("username", user.getUsername());
session.set("realName", user.getRealName());
return R.ok(new LoginVO(token, user));
}
@PostMapping("/logout")
public R<Void> logout() {
StpUtil.logout();
return R.ok();
}
@GetMapping("/userinfo")
public R<User> getUserInfo() {
Long userId = StpUtil.getLoginIdAsLong();
User user = userService.getById(userId);
return R.ok(user);
}
}密码加密
java
import cn.dev33.satoken.secure.BCrypt;
// 加密密码
String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt());
// 验证密码
boolean isMatch = BCrypt.checkpw(password, hashedPassword);权限验证
手动验证
java
@GetMapping("/list")
public R<List<User>> list() {
// 验证权限
StpUtil.checkPermission("system:user:list");
return R.ok(userList);
}
// 验证多个权限(必须全部拥有)
StpUtil.checkPermissionAnd("system:user:create", "system:user:edit");
// 验证多个权限(拥有其中一个即可)
StpUtil.checkPermissionOr("system:user:edit", "system:user:update");
// 判断是否拥有权限(不抛出异常)
if (StpUtil.hasPermission("system:user:detail")) {
// 有权限
}权限加载
实现 StpInterface 接口:
java
@Component
public class StpInterfaceImpl implements StpInterface {
@Autowired
private UserService userService;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
return userService.getPermissionsByUserId(userId);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
Long userId = Long.valueOf(loginId.toString());
return userService.getRolesByUserId(userId);
}
}角色验证
java
// 验证角色
StpUtil.checkRole("admin");
// 验证多个角色(必须全部拥有)
StpUtil.checkRoleAnd("admin", "super-admin");
// 验证多个角色(拥有其中一个即可)
StpUtil.checkRoleOr("admin", "manager");
// 判断是否拥有角色
if (StpUtil.hasRole("admin")) {
// 有角色
}注解鉴权
常用注解
java
// 登录验证
@SaCheckLogin
@GetMapping("/profile")
public R<User> getProfile() {
return R.ok(user);
}
// 权限验证
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public R<List<User>> list() {
return R.ok(userList);
}
// 多个权限(AND 关系)
@SaCheckPermission(value = {"system:user:create", "system:user:edit"}, mode = SaMode.AND)
@PostMapping
public R<Void> create(@RequestBody User user) {
return R.ok();
}
// 角色验证
@SaCheckRole("admin")
@DeleteMapping("/{id}")
public R<Void> delete(@PathVariable Long id) {
return R.ok();
}
// 忽略认证
@SaIgnore
@GetMapping("/public-info")
public R<Map<String, Object>> getPublicInfo() {
return R.ok(publicInfo);
}Session 会话
Session 操作
java
// 获取 Session
SaSession session = StpUtil.getSession();
// 设置数据
session.set("username", "admin");
session.set("email", "admin@example.com");
// 获取数据
String username = session.getString("username");
Integer loginCount = session.getInt("loginCount");
// 删除数据
session.delete("key");
// 清空 Session
session.clear();Token 管理
Token 信息
java
// 获取 Token 信息
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 获取 Token 剩余有效期
long timeout = StpUtil.getTokenTimeout();
// 续签 Token
StpUtil.renewTimeout(2592000);
// 获取当前用户的所有 Token
List<String> tokenList = StpUtil.getTokenValueListByLoginId(userId);踢人下线
java
// 踢掉指定用户
StpUtil.kickout(userId);
// 踢掉指定 Token
StpUtil.kickoutByTokenValue(token);
// 封禁用户
StpUtil.disable(userId, seconds);
// 解除封禁
StpUtil.untieDisable(userId);在线用户管理
java
@GetMapping("/online/list")
public R<List<OnlineUser>> getOnlineUsers() {
List<OnlineUser> onlineUsers = new ArrayList<>();
// 获取所有在线用户的 Session
List<String> sessionIdList = StpUtil.searchSessionId("", 0, -1, false);
for (String sessionId : sessionIdList) {
String userId = sessionId.replace("satoken:login:session:", "");
SaSession session = StpUtil.getSessionByLoginId(userId);
OnlineUser onlineUser = new OnlineUser();
onlineUser.setUserId(Long.valueOf(userId));
onlineUser.setUsername(session.getString("username"));
onlineUser.setLoginTime(session.getLong("loginTime"));
onlineUsers.add(onlineUser);
}
return R.ok(onlineUsers);
}异常处理
java
@RestControllerAdvice
public class SaTokenExceptionHandler {
@ExceptionHandler(NotLoginException.class)
public R<Void> handleNotLoginException(NotLoginException e) {
String message = "";
switch (e.getType()) {
case NotLoginException.NOT_TOKEN:
message = "未提供 Token";
break;
case NotLoginException.INVALID_TOKEN:
message = "Token 无效";
break;
case NotLoginException.TOKEN_TIMEOUT:
message = "Token 已过期";
break;
case NotLoginException.BE_REPLACED:
message = "Token 已被顶下线";
break;
case NotLoginException.KICK_OUT:
message = "Token 已被踢下线";
break;
default:
message = "未登录";
}
return R.fail(401, message);
}
@ExceptionHandler(NotPermissionException.class)
public R<Void> handleNotPermissionException(NotPermissionException e) {
return R.fail(403, "权限不足:" + e.getPermission());
}
@ExceptionHandler(NotRoleException.class)
public R<Void> handleNotRoleException(NotRoleException e) {
return R.fail(403, "角色不足:" + e.getRole());
}
}工具类封装
java
@Component
public class LoginUserUtils {
private static UserService userService;
public LoginUserUtils(UserService userService) {
LoginUserUtils.userService = userService;
}
public static Long getUserId() {
try {
return StpUtil.getLoginIdAsLong();
} catch (Exception e) {
return null;
}
}
public static User getUser() {
Long userId = getUserId();
if (userId == null) {
return null;
}
return userService.getById(userId);
}
public static String getUsername() {
User user = getUser();
return user != null ? user.getUsername() : null;
}
public static boolean isLogin() {
return StpUtil.isLogin();
}
public static boolean hasPermission(String permission) {
return StpUtil.hasPermission(permission);
}
public static boolean hasRole(String role) {
return StpUtil.hasRole(role);
}
}常见问题
Q: Token 一直提示过期?
yaml
sa-token:
timeout: 2592000 # 30天
active-timeout: -1 # 永不冻结Q: 权限验证不生效?
确保实现了 StpInterface 接口并正确返回权限列表。
Q: 多端登录互相踢下线?
yaml
sa-token:
is-concurrent: true # 允许多端同时登录Q: 前端请求返回 401?
确保前端请求头正确携带 Token:
javascript
axios.get('/api/user/info', {
headers: {
'Authorization': 'Bearer ' + token
}
});