一、Sa-Token简介
Sa-Token是一款轻量级Java权限认证框架,专为解决Web系统的登录认证、权限控制、Session会话等问题而设计。其核心优势在于API简洁、功能丰富、扩展性强,支持注解鉴权、多账号体系、单点登录等场景相比Shiro和Spring Security,Sa-Token的学习成本和集成难度更低,适合快速开发。
Sa-token功能概览
Sa-Token 目前主要五大功能模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
- 登录认证 —— 单端登录、多端登录、同端互斥登录、七天内免登录。
- 权限认证 —— 权限认证、角色认证、会话二级认证。
- 踢人下线 —— 根据账号id踢人下线、根据Token值踢人下线。
- 注解式鉴权 —— 优雅的将鉴权与业务代码分离。
- 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配 restful 模式。
- Session会话 —— 全端共享Session,单端独享Session,自定义Session,方便的存取值。
- 持久层扩展 —— 可集成 Redis,重启数据不丢失。
- 前后台分离 —— APP、小程序等不支持 Cookie 的终端也可以轻松鉴权。
- Token风格定制 —— 内置六种 Token 风格,还可:自定义 Token 生成策略。
- 记住我模式 —— 适配 [记住我] 模式,重启浏览器免验证。
- 二级认证 —— 在已登录的基础上再次认证,保证安全性。
- 模拟他人账号 —— 实时操作任意用户状态数据。
- 临时身份切换 —— 将会话身份临时切换为其它账号。
- 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录。
- 账号封禁 —— 登录封禁、按照业务分类封禁、按照处罚阶梯封禁。
- 密码加密 —— 提供基础加密算法,可快速 MD5、SHA1、SHA256、AES 加密。
- 会话查询 —— 提供方便灵活的会话查询接口。
- Http Basic认证 —— 一行代码接入 Http Basic、Digest 认证。
- 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
- 全局过滤器 —— 方便的处理跨域,全局设置安全响应头等操作。
- 多账号体系认证 —— 一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
- 单点登录 —— 内置三种单点登录模式:同域、跨域、同Redis、跨Redis、前后端分离等架构都可以搞定。
- 单点注销 —— 任意子系统内发起注销,即可全端下线。
- OAuth2.0认证 —— 轻松搭建 OAuth2.0 服务,支持openid模式 。
- 分布式会话 —— 提供共享数据中心分布式会话方案。
- 微服务网关鉴权 —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证。
- RPC调用鉴权 —— 网关转发鉴权,RPC调用鉴权,让服务调用不再裸奔
- 临时Token认证 —— 解决短时间的 Token 授权问题。
- 独立Redis —— 将权限缓存与业务缓存分离。
- Quick快速登录认证 —— 为项目零代码注入一个登录页面。
- 标签方言 —— 提供 Thymeleaf 标签方言集成包,提供 beetl 集成示例。
- jwt集成 —— 提供三种模式的 jwt 集成方案,提供 token 扩展参数能力。
- RPC调用状态传递 —— 提供 dubbo、grpc 等集成包,在RPC调用时登录状态不丢失。
- 参数签名 —— 提供跨系统API调用签名校验模块,防参数篡改,防请求重放。
- 自动续签 —— 提供两种Token过期策略,灵活搭配使用,还可自动续签。
- 开箱即用 —— 提供SpringMVC、WebFlux、Solon 等常见框架集成包,开箱即用。
- 最新技术栈 —— 适配最新技术栈:支持 SpringBoot 3.x,jdk 17。
项目官网:https://sa-token.cc/index.html
二、环境准备
使用springboot前确保已经安装JDK,使用这行命令即可查看当前JDK版本。
java -version
如果没有安装JDK请自行到Oracle官网下载安装,推荐JDK版本在17及以上。
下面将sa-token的起步依赖导入到springboot项目中。
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot3-starter</artifactId><version>1.40.0</version></dependency>
如果不是springboot3.x版本以上的起步依赖导入下面这个
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.34.0</version>
</dependency>
三、核心功能实现
在application.yml文件中添加如下配置:
spring:application:name: spring-sa-token
server:port: 8081#sa-token configuration
sa-token:token-name: satokenactive-timeout: -1same-token-timeout: 2592000is-concurrent: falseis-share: falsetoken-style: uuidis-log: true
1.登录认证
下面我们进行模拟登录认证的流程,新建一个controller,导入以下代码:
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 登录测试*/
@RestController
@RequestMapping("/acc/")
public class LoginController {// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456@RequestMapping("doLogin")public SaResult doLogin(String name, String pwd) {// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对if("zhang".equals(name) && "123456".equals(pwd)) {StpUtil.login(10001);return SaResult.ok("登录成功");}return SaResult.error("登录失败");}// 查询登录状态 ---- http://localhost:8081/acc/isLogin@RequestMapping("isLogin")public SaResult isLogin() {return SaResult.ok("是否登录:" + StpUtil.isLogin());}// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo@RequestMapping("tokenInfo")public SaResult tokenInfo() {return SaResult.data(StpUtil.getTokenInfo());}// 测试注销 ---- http://localhost:8081/acc/logout@RequestMapping("logout")public SaResult logout() {StpUtil.logout();return SaResult.ok();}}
启动springboot工程后在浏览器输入地址测试。
首先测试登录功能
登录成功后查询登录状态:
查询token信息:
测试退出登录:
2.权限认证
权限认证就是查询一下有没有权限,有就通过,没有就禁止访问。
例如:当前账号拥有权限码集合 ["user-add", "user-delete", "user-get"]
,这时候我来校验权限 "user-update"
,则其结果就是:验证失败,禁止访问。
import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 自定义权限加载接口实现类*/
@Component // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {/*** 返回一个账号所拥有的权限码集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询权限List<String> list = new ArrayList<String>();list.add("101");list.add("user.add");list.add("user.update");list.add("user.get");// list.add("user.delete");list.add("art.*");return list;}/*** 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {// 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询角色List<String> list = new ArrayList<String>();list.add("admin");list.add("super-admin");return list;}}
在案例中,我们重写了StpInterface接口中的getPermissionList和getRoleList集合,这样权限就加入到标识集合中,用户访问某个接口时会去查询权限集合中的权限,如果用户有此权限就通过,反之就禁止访问。
3.踢人下线
所谓踢人下线,核心操作就是找到指定 loginId
对应的 Token
,并设置其失效。
强制注销
StpUtil.logout(10001); // 强制指定账号注销下线
StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线
StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线
踢人下线
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线
强制注销 和 踢人下线 的区别在于:
强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。
踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
4.注解鉴权
注解鉴权 —— 优雅的将鉴权与业务代码分离!
@SaCheckLogin
: 登录校验 —— 只有登录之后才能进入该方法。@SaCheckRole("admin")
: 角色校验 —— 必须具有指定角色标识才能进入该方法。@SaCheckPermission("user:add")
: 权限校验 —— 必须具有指定权限才能进入该方法。@SaCheckSafe
: 二级认证校验 —— 必须二级认证之后才能进入该方法。@SaCheckHttpBasic
: HttpBasic校验 —— 只有通过 HttpBasic 认证后才能进入该方法。@SaCheckHttpDigest
: HttpDigest校验 —— 只有通过 HttpDigest 认证后才能进入该方法。@SaIgnore
:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。@SaCheckDisable("comment")
:账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。
Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
因此,为了使用注解鉴权,必须手动将 Sa-Token 的全局拦截器注册到项目中
手动注册拦截器
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {// 注册 Sa-Token 拦截器,打开注解式鉴权功能 @Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册 Sa-Token 拦截器,打开注解式鉴权功能 registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); }
}
注册拦截器之后就可以使用注解鉴权
// 登录校验:只有登录之后才能进入该方法
@SaCheckLogin
@RequestMapping("info")
public String info() {return "查询用户信息";
}// 角色校验:必须具有指定角色才能进入该方法
@SaCheckRole("super-admin")
@RequestMapping("add")
public String add() {return "用户增加";
}// 权限校验:必须具有指定权限才能进入该方法
@SaCheckPermission("user-add")
@RequestMapping("add")
public String add() {return "用户增加";
}// 二级认证校验:必须二级认证之后才能进入该方法
@SaCheckSafe()
@RequestMapping("add")
public String add() {return "用户增加";
}// Http Basic 校验:只有通过 Http Basic 认证后才能进入该方法
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("add")
public String add() {return "用户增加";
}// Http Digest 校验:只有通过 Http Digest 认证后才能进入该方法
@SaCheckHttpDigest(value = "sa:123456")
@RequestMapping("add")
public String add() {return "用户增加";
}// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
@SaCheckDisable("comment")
@RequestMapping("send")
public String send() {return "查询用户信息";
}
设定校验模式
@SaCheckRole
与@SaCheckPermission
注解可以进行设置校验模式
// 注解式鉴权:只要具有其中一个权限即可通过校验
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public SaResult atJurOr() {return SaResult.data("用户信息");
}
mode有两种取值:
SaMode.AND
,标注一组权限,会话必须全部具有才可通过校验。SaMode.OR
,标注一组权限,会话只要具有其一即可通过校验。
角色权限双重验证
假设有以下业务场景:一个接口在具有权限 user.add
或角色 admin
时可以调通。怎么写?
// 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")
public SaResult userAdd() {return SaResult.data("用户信息");
}
orRole 字段代表权限校验未通过时的次要选择,两者只要其一校验成功即可进入请求方法,其有三种写法:
- 写法一:
orRole = "admin"
,代表需要拥有角色 admin 。 - 写法二:
orRole = {"admin", "manager", "staff"}
,代表具有三个角色其一即可。 - 写法三:
orRole = {"admin, manager, staff"}
,代表必须同时具有三个角色。
忽略认证
@SaCheckLogin
@RestController
public class TestController {// ... 其它方法 // 此接口加上了 @SaIgnore 可以游客访问 @SaIgnore@RequestMapping("getList")public SaResult getList() {// ... return SaResult.ok(); }
}
5.路由注解鉴权
注册Sa-token路由拦截器
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {// 注册拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())).addPathPatterns("/**").excludePathPatterns("/user/doLogin"); }
}
以上代码注册了一个基于 StpUtil.checkLogin()
的登录校验拦截器,并且排除了/user/doLogin
接口用来开放登录(除了/user/doLogin
以外的所有接口都需要登录才能访问)。
校验函数详解
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {// 注册 Sa-Token 的拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册路由拦截器,自定义认证规则 registry.addInterceptor(new SaInterceptor(handler -> {// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());// 角色校验 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证 SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));// 权限校验 -- 不同模块校验不同权限 SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));// 甚至你可以随意的写一个打印语句SaRouter.match("/**", r -> System.out.println("----啦啦啦----"));// 连缀写法SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----"));})).addPathPatterns("/**");}
}
6.自定义token风格
// 1. token-style=uuid —— uuid风格 (默认风格)
"623368f0-ae5e-4475-a53f-93e4225f16ae"// 2. token-style=simple-uuid —— 同上,uuid风格, 只不过去掉了中划线
"6fd4221395024b5f87edd34bc3258ee8"// 3. token-style=random-32 —— 随机32位字符串
"qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W"// 4. token-style=random-64 —— 随机64位字符串
"v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc"// 5. token-style=random-128 —— 随机128位字符串
"nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj"// 6. token-style=tik —— tik风格
"gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"
7.账号封禁
对指定账号进行封禁:
// 封禁指定账号
StpUtil.disable(10001, 86400);
参数含义:
- 参数1:要封禁的账号id。
- 参数2:封禁时间,单位:秒,此为 86400秒 = 1天(此值为 -1 时,代表永久封禁)。
对于正在登录的账号,将其封禁并不会使它立即掉线,如果我们需要它即刻下线,可采用先踢再封禁的策略
// 先踢下线
StpUtil.kickout(10001);
// 再封禁账号
StpUtil.disable(10001, 86400);
待到下次登录时,我们先校验一下这个账号是否已被封禁
// 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(10001); // 通过校验后,再进行登录:
StpUtil.login(10001);
账号封禁模块的所有方法
// 封禁指定账号
StpUtil.disable(10001, 86400); // 获取指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
StpUtil.isDisable(10001); // 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(10001); // 获取指定账号剩余封禁时间,单位:秒,如果该账号未被封禁,则返回-2
StpUtil.getDisableTime(10001); // 解除封禁
StpUtil.untieDisable(10001);
8.密码加密
常见的加密算法
// md5加密
SaSecureUtil.md5("123456");// sha1加密
SaSecureUtil.sha1("123456");// sha256加密
SaSecureUtil.sha256("123456");
对称加密
// 定义秘钥和明文
String key = "123456";
String text = "Sa-Token 一个轻量级java权限认证框架";// 加密
String ciphertext = SaSecureUtil.aesEncrypt(key, text);
System.out.println("AES加密后:" + ciphertext);// 解密
String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
System.out.println("AES解密后:" + text2);
非对称加密
// 定义私钥和公钥
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2);
生成公、私钥
// 生成一对公钥和私钥,其中Map对象 (private=私钥, public=公钥)
System.out.println(SaSecureUtil.rsaGenerateKeyPair());
Base64编码与解码
// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";// 使用Base64编码
String base64Text = SaBase64Util.encode(text);
System.out.println("Base64编码后:" + base64Text);// 使用Base64解码
String text2 = SaBase64Util.decode(base64Text);
System.out.println("Base64解码后:" + text2);
Bcrypt加密
// 使用方法
String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); // 使用checkpw方法检查被加密的字符串是否与原始字符串匹配:
BCrypt.checkpw(candidate_password, stored_hash); // gensalt方法提供了可选参数 (log_rounds) 来定义加盐多少,也决定了加密的复杂度:
String strong_salt = BCrypt.gensalt(10);
String stronger_salt = BCrypt.gensalt(12);
9.全局监听器
Sa-Token 提供一种侦听器机制,通过注册侦听器,你可以订阅框架的一些关键性事件,例如:用户登录、退出、被踢下线等。
自定义监听器实现
/*** 自定义侦听器的实现 */
@Component
public class MySaTokenListener implements SaTokenListener {/** 每次登录时触发 */@Overridepublic void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {System.out.println("---------- 自定义侦听器实现 doLogin");}/** 每次注销时触发 */@Overridepublic void doLogout(String loginType, Object loginId, String tokenValue) {System.out.println("---------- 自定义侦听器实现 doLogout");}/** 每次被踢下线时触发 */@Overridepublic void doKickout(String loginType, Object loginId, String tokenValue) {System.out.println("---------- 自定义侦听器实现 doKickout");}/** 每次被顶下线时触发 */@Overridepublic void doReplaced(String loginType, Object loginId, String tokenValue) {System.out.println("---------- 自定义侦听器实现 doReplaced");}/** 每次被封禁时触发 */@Overridepublic void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {System.out.println("---------- 自定义侦听器实现 doDisable");}/** 每次被解封时触发 */@Overridepublic void doUntieDisable(String loginType, Object loginId, String service) {System.out.println("---------- 自定义侦听器实现 doUntieDisable");}/** 每次二级认证时触发 */@Overridepublic void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {System.out.println("---------- 自定义侦听器实现 doOpenSafe");}/** 每次退出二级认证时触发 */@Overridepublic void doCloseSafe(String loginType, String tokenValue, String service) {System.out.println("---------- 自定义侦听器实现 doCloseSafe");}/** 每次创建Session时触发 */@Overridepublic void doCreateSession(String id) {System.out.println("---------- 自定义侦听器实现 doCreateSession");}/** 每次注销Session时触发 */@Overridepublic void doLogoutSession(String id) {System.out.println("---------- 自定义侦听器实现 doLogoutSession");}/** 每次Token续期时触发 */@Overridepublic void doRenewTimeout(String tokenValue, Object loginId, long timeout) {System.out.println("---------- 自定义侦听器实现 doRenewTimeout");}
}
10.全局过滤器
拦截器与过滤器区别:
- 相比于拦截器,过滤器更加底层,执行时机更靠前,有利于防渗透扫描。
- 过滤器可以拦截静态资源,方便我们做一些权限控制。
- 部分Web框架根本就没有提供拦截器功能,但几乎所有的Web框架都会提供过滤器机制。
但是过滤器也有一些缺点,比如:
- 由于太过底层,导致无法率先拿到
HandlerMethod
对象,无法据此添加一些额外功能。 - 由于拦截的太全面了,导致我们需要对很多特殊路由(如
/favicon.ico
)做一些额外处理。 - 在Spring中,过滤器中抛出的异常无法进入全局
@ExceptionHandler
,我们必须额外编写代码进行异常处理。
Sa-Token同时提供过滤器和拦截器机制,不是为了让谁替代谁,而是为了让大家根据自己的实际业务合理选择,拥有更多的发挥空间。
/*** [Sa-Token 权限认证] 配置类 */
@Configuration
public class SaTokenConfigure {/*** 注册 [Sa-Token全局过滤器] */@Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter()// 指定 拦截路由 与 放行路由.addInclude("/**").addExclude("/favicon.ico") /* 排除掉 /favicon.ico */// 认证函数: 每次请求执行 .setAuth(obj -> {System.out.println("---------- 进入Sa-Token全局认证 -----------");// 登录认证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());// 更多拦截处理方式,请参考“路由拦截式鉴权”章节 */})// 异常处理函数:每次认证函数发生异常时执行此函数 .setError(e -> {System.out.println("---------- 进入Sa-Token异常处理 -----------");return SaResult.error(e.getMessage());})// 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入).setBeforeAuth(r -> {// ---------- 设置一些安全响应头 ----------SaHolder.getResponse()// 服务器名称 .setServer("sa-server")// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以 .setHeader("X-Frame-Options", "SAMEORIGIN")// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面 .setHeader("X-XSS-Protection", "1; mode=block")// 禁用浏览器内容嗅探 .setHeader("X-Content-Type-Options", "nosniff");});}}
四、常见问题
- Token失效场景:默认30天无操作失效,可通过
activity-timeout
调整 - 多端登录控制:设置
is-concurrent: false
禁止并发登录 - 跨域问题:需在拦截器中配置CORS或使用
@CrossOrigin
注解
五、总结
Sa-Token优势:
- 开箱即用,API简洁
- 支持注解鉴权与RBAC模型
- 无缝整合Spring生态
- 提供JWT、单点登录等扩展方案
适用场景:中小型Web应用、微服务架构、快速开发项目