Skip to content

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
  }
});

参考资源

MIT License