Spring Boot整合Sa-Token框架(入门篇)

Spring Security、Apache Shiro、Sa-Token是Java Web开发中常用的主流权限认证框架。

三种框架的上手难度依次递减:Spring Security > Apache Shiro > Sa-Token


如果你使用过Apache Shiro,你会发现:

  • Sa-Token和Apache Shiro有许多相似点
    • 通配符权限:分隔符不同
      • Sa-Token:user.*、user.login
      • Apache Shiro:user:*、user:login
  • Apache Shiro对于前后端分离项目不友好

本篇文章将结合博主的Sa-Token框架使用经验和官方文档,通过一整篇文章详细介绍在Java开发神器(Spring Boot)中使用Sa-Token来完成权限认证功能。


创建项目

在Intellij IDEA中创建一个Maven项目,名字就叫sa-token-study。

在这里就不直接通过Spring Initializer创建Spring Boot项目了,因为默认会使用较新的Spring Boot版本,所以会触发Maven依赖包的自动下载。

添加依赖

在Maven的配置文件pom.xml中,添加Sa-Token的必要依赖。

同时,设置当前Maven继承spring-boot-starter-parent,添加Spring Boot的启动器依赖。

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <version>2.3.4.RELEASE</version>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
    </parent>
    
    <version>20260531</version>
    <groupId>cn.edu.sgu.www</groupId>
    <artifactId>sa-token-study</artifactId>

    <properties>
        <sa-token.version>1.45.0</sa-token.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Sa-Token -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
    </dependencies>
</project>

修改配置

创建配置文件

因为这是一个Maven项目,Spring Boot的配置文件需要手动创建。

在src/main/resources目录下创建一个application.yml文件。

添加框架配置

直接从Sa-Token官方文档复制配置文件内容,把配置sa-token.token-name的值改成了项目名。

XML 复制代码
server:
    # 端口
    port: 8081
    
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token: 
    # token 名称(同时也是 cookie 名称)
    token-name: sa-token-study
    # token 有效期(单位:秒) 默认30天,-1 代表永久有效
    timeout: 2592000
    # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
    active-timeout: -1
    # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
    is-concurrent: true
    # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
    is-share: false
    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否输出操作日志 
    is-log: true

补全项目

将当前Maven项目完全变成Spring Boot项目。

创建项目根包

在src/main/java包下创建多级包cn.edu.sgu.www.study

创建启动类

在项目根包cn.edu.sgu.www.study下创建Spring Boot启动类。

java 复制代码
package cn.edu.sgu.www.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@SpringBootApplication
public class SaTokenStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SaTokenStudyApplication.class, args);
    }

}

登录认证

Sa-Token中登录认证只需要一句代码,调用StpUtil工具类的login()方法,把账号ID作为参数。

XML 复制代码
StpUtil.login(Object userId);

案例代码:登录ID为10086的账号

java 复制代码
StpUtil.login(10086); 

保存用户信息

通常,我们希望像HttpSession一样,将用户的信息保存到当前会话中,以便在其他地方使用。

java 复制代码
SaSession session = StpUtil.getSession();
User user = new User();

session.set("user", user);

获取用户信息

java 复制代码
SaSession session = StpUtil.getSession();

Object obj = session.get("user");

User user = (User) obj;

// todo:访问user

登录认证案例

创建控制器类

在项目根包cn.edu.sgu.www.study下创建controller.LoginController类。

定义一个登录方法login(),该方法接收一个userId参数。

java 复制代码
package cn.edu.sgu.www.study.controller;

import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@RestController
public class LoginController {

    @GetMapping("/login")
    public void login(String userId) {
        StpUtil.login(userId);
        
        System.out.println("登录成功");
    }

}

测试登录接口

运行启动类SaTokenStudyApplication的main()方法,启动当前Spring Boot项目。

在浏览器地址栏输入以下网址访问登录接口

java 复制代码
http://localhost:8081/login?userId=10086

在Intellij IDEA的控制台,可以看到方法里打印的登录成功信息和Sa-Token登录产生的会话信息。

权限认证

权限校验

可以使用以下方法来完成权限的校验。

java 复制代码
// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();

// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.insert");

// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermission("user.insert");

// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.insert", "user.delete", "user.update");

// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.insert", "user.delete", "user.update");  

角色校验

可以使用以下方法来完成角色的校验。

java 复制代码
// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();

// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("sys-admin");

// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("sys-admin");

// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("sys-admin", "sys-user");

// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
StpUtil.checkRoleOr("sys-admin", "sys-user");

提供权限

可以通过提供一个StpInterface类型的Bean来根据账号ID登录类型为对应账号设置权限和角色。

java 复制代码
package cn.edu.sgu.www.study.support;

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

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

/**
 * 权限数据源加载类
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        List<String> list = new ArrayList<>();
        
        list.add("user.insert");
        list.add("user.delete");
        list.add("user.update");
        
        return list;
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        List<String> list = new ArrayList<>();

        list.add("sys-admin");
        list.add("sys-user");
        
        return list;
    }

}

鉴权方式

注解鉴权

Sa-Token框架提供了以下注解用于鉴权,只需要在对应的控制器接口方法上使用这些注解。

常用的注解

  • @SaIgnore:忽略校验 ------ 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
  • @SaCheckLogin:登录校验 ------ 只有登录之后才能进入该方法。
  • @SaCheckRole("admin"):角色校验 ------ 必须具有指定角色标识才能进入该方法。
  • @SaCheckPermission("user:add"):权限校验 ------ 必须具有指定权限才能进入该方法。

不常用的注解

  • @SaCheckSafe:二级认证校验 ------ 必须二级认证之后才能进入该方法。
  • @SaCheckHttpBasic:HttpBasic校验 ------ 只有通过 HttpBasic 认证后才能进入该方法。
  • @SaCheckHttpDigest:HttpDigest校验 ------ 只有通过 HttpDigest 认证后才能进入该方法。
  • @SaCheckDisable("comment"):账号服务封禁校验 ------ 校验当前账号指定服务是否被封禁。
  • @SaCheckSign:API 签名校验 ------ 用于跨系统的 API 签名参数校验。
创建控制器

在项目根包cn.edu.sgu.www.study.controller下创建UserController类。


增加3个HTTP接口:/user/insert、/user/delete、/user/update

为了能够直接通过浏览器地址栏访问,将接口的HTTP请求方法都设置为GET。

java 复制代码
package cn.edu.sgu.www.study.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    @GetMapping("/insert")
    public void insert() {
        System.out.println("添加用户");
    }
    
    @GetMapping("/delete")
    public void delete() {
        System.out.println("删除用户");
    }
    
    @GetMapping("/update")
    public void update() {
        System.out.println("修改用户");
    }

}
注册拦截器

注解鉴权是基于AOP(拦截器)完成的,需要添加Sa-Token的拦截器到Spring MVC中。

java 复制代码
package cn.edu.sgu.www.study.config;

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

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册Sa-Token拦截器,打开注解式鉴权功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
    }

}
使用鉴权注解

在三个接口上使用@SaCheckPermission注解,配置需要指定的权限才能访问对应的方法。

java 复制代码
package cn.edu.sgu.www.study.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @SaCheckPermission("user.insert")
    @GetMapping("/insert")
    public void insert() {
        System.out.println("添加用户");
    }

    @SaCheckPermission("user.delete")
    @GetMapping("/delete")
    public void delete() {
        System.out.println("删除用户");
    }

    @SaCheckPermission("user.update")
    @GetMapping("/update")
    public void update() {
        System.out.println("修改用户");
    }

}
测试注解鉴权

启动当前Spring Boot项目,在浏览器地址栏输入以下网址分别访问三个接口。

java 复制代码
http://localhost:8081/user/insert

http://localhost:8081/user/delete

http://localhost:8081/user/update
访问失败

访问3个接口都触发了服务器异常。

Intellij IDEA的控制台打印了3个一样的异常的栈信息,这是因为没有登录,之前登录的token已经在项目重启之后失效了(存在了内存中,被删除了)。

完成登录

为了解决这个问题,需要在访问这些接口之前,通过登录接口登录。

java 复制代码
http://localhost:8081/login?userId=10086
鉴权成功

登录完成后,在浏览器地址栏输入以下网址分别访问三个接口。

java 复制代码
http://localhost:8081/user/insert

http://localhost:8081/user/delete

http://localhost:8081/user/update

在Intellij IDEA的控制台,可以看到3个方法里打印的信息。

鉴权失败

注释掉设置的权限,然后重启项目,再次访问三个接口。

从控制台打印的异常栈信息,可以看到没有对应的user.insert权限。

可以用相似的方式测试@SaCheckRole、@SaCheckLogin等其他注解。

路由拦截鉴权

注解鉴权固然方便,但是不利于维护,更新时需要到每个控制器类中去修改。

于是,Sa-Token的另外一种鉴权方式更适合大多数Web应用:路由拦截鉴权。

可以在过滤器、拦截器中动态配置访问某个(某些)url需要哪个(哪些)权限。


为什么使用路由拦截鉴权?

  • 路由拦截鉴权的最大特点就是动态,可以动态配置路由权限。
  • 比起直接在代码里写死,这种方式无疑更加灵活,也更易于理解。
拦截器鉴权

由于刚学完基于拦截器的注解鉴权,就先介绍基于拦截器的路由拦截鉴权。


使用基于拦截器的路由拦截鉴权,只需要在拦截器中配置路由和需要的对应权限,并且关闭注解鉴权,避免一个url被鉴权两次的bug。

java 复制代码
package cn.edu.sgu.www.study.config;

import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.router.SaRouterStaff;
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;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册Sa-Token拦截器
        registry.addInterceptor(saInterceptor()).addPathPatterns("/**");
    }

    private SaInterceptor saInterceptor() {
        return new SaInterceptor(new SaParamFunction<Object>() {
            @Override
            public void run(Object handler) {
                SaRouter.match("/user/insert").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.insert");
                    }
                });
                SaRouter.match("/user/delete").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.delete");
                    }
                });
                SaRouter.match("/user/update").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.update");
                    }
                });
            }
        }).isAnnotation(false);
    }

}
过滤器鉴权

路由拦截鉴权除了基于拦截器的鉴权方式以外,还可以通过配置过滤器来完成鉴权操作。

java 复制代码
package cn.edu.sgu.www.study.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.router.SaRouterStaff;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 沐雨橙风ιε
 * @version 1.0
 */
@Configuration
public class SaTokenConfig {

    /**
     * 注册Sa-Token全局过滤器
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter().setAuth(new SaFilterAuthStrategy() {
            @Override
            public void run(Object obj) {
                SaRouter.match("/user/insert").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.insert");
                    }
                });
                SaRouter.match("/user/delete").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.delete");
                    }
                });
                SaRouter.match("/user/update").check(new SaParamFunction<SaRouterStaff>() {
                    @Override
                    public void run(SaRouterStaff r) {
                        StpUtil.checkPermission("user.update");
                    }
                });
            }
        }).setError(new SaFilterErrorStrategy() {
            @Override
            public Object run(Throwable throwable) {
                String message = null;
                int code = 200;

                // 不同类型的异常区别处理
                if (throwable instanceof NotLoginException) {
                    message = "请登录后访问!";
                    code = 403;
                } else if (throwable instanceof NotRoleException || throwable instanceof NotPermissionException) {
                    message = "正在访问未授权的资源!";

                    code = 401;
                }

                SaResponse response = SaHolder.getResponse();

                response.setStatus(code);

                return new SaResult(code, message, null);
            }
        }).addInclude("/**");
    }

}

使用全局过滤器之后,就不需要注册Sa-Token的拦截器了。

会话共享

Sa-Token默认将登录会话信息保存在内存中(java.util.Map),所以重启应用将导致会话丢失。

对此,Sa-Token实现了Redis的无缝集成,只需要添加一个依赖,就可以将会话存储在Redis中,实现会话的共享。

XML 复制代码
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-template</artifactId>
    <version>1.45.0</version>
</dependency>

完整的代码已经保存到码云,需要的自行拉取~

Spring Boot整合Sa-Token权限认证框架https://gitee.com/muyu-chengfeng/sa-token-study.git

相关推荐
NE_STOP1 小时前
Docker--初识Dockerfile
java
小楊不秃头1 小时前
SpringBoot: IoC&DI
spring boot·ioc·di
绝知此事2 小时前
ELK 从入门到精通:Spring Boot 实战三部曲(三)—— 高级应用与架构设计
spring boot·后端·elk
程序员海军2 小时前
我用了 8 个月 Codex CLI,总结出这套 AI 编程工作流
前端·后端·aigc
我是一颗柠檬2 小时前
【Redis】列表与集合Day4(2026年)
数据库·redis·后端·缓存
techdashen2 小时前
Rust 中的小字符串:smol_str 与 smartstring 的对决
开发语言·后端·rust
码哥字节2 小时前
升到 Spring Boot 4.1,虚拟线程开了,HikariCP 连接池却崩了
java·springboot·claude code
devilnumber2 小时前
java自定义事件处理器极简版:「外卖点餐」场景
java·开发语言