Sa-Token介绍与SpringBoot环境下使用
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
官网:Sa-Token
一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!
介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。
官方文档写的已经非常好了。引用官方文档开头的一段话:
本文档将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 Sa-Token 框架本身,更是绝大多数场景下权限设计的最佳实践。
确实,通过阅读官方文档有学到很多东西,收获更大的是结合我的使用体验,下载并阅读源码后有学到了一些东西想和大家分享!这篇文章只是开头。
使用
1、准备工作
环境:
Spring Boot2.7.3
Sa-Token1.37.0
2、配置文件
关于sa-token的配置文件如下
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: Authorization
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 3600
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 1800
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: false
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: tik
# 是否输出操作日志
is-log: true
3、自定义权限认证
官方文档:因为每个项目的需求不同,其权限设计也千变万化,因此 [ 获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token 将此操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。
所以根据自己的不同业务设计需要的权限认证方法就好。下面是我项目中的写法,仅供参考。
@Setter
public class StpInterfaceImpl implements StpInterface {
/**
* 登录服务
*/
private LoginService loginService;
/**
* 返回指定账号id所拥有的权限码集合
*
* @param loginId 账号id
* @param loginType 账号类型
* @return 该账号id具有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
Login loginUser = loginService.getLoginUser();
UserTypeEnum userType = UserTypeEnum.valueOf(loginUser.getType());
if (userType == UserTypeEnum.PC) {
return new ArrayList<>(loginUser.getPermissions());
}
return new ArrayList<>();
}
/**
* 返回指定账号id所拥有的角色标识集合
*
* @param loginId 账号id
* @param loginType 账号类型
* @return 该账号id具有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
Login loginUser = loginService.getLoginUser();
UserTypeEnum userType = UserTypeEnum.valueOf(loginUser.getType());
if (userType == UserTypeEnum.PC) {
return new ArrayList<>(loginUser.getRoleValues());
}
return new ArrayList<>();
}
}
接着注册StpInterfaceImpl
@Configuration
public class SaTokenConfiguration {
@Bean
public LoginService loginService() {
return new LoginServiceImpl();
}
@Bean
public StpInterface stpInterface(LoginService loginService) {
StpInterfaceImpl stpInterface = new StpInterfaceImpl();
stpInterface.setLoginService(loginService);
return stpInterface;
}
}
4、Controller
4.1、工具类鉴权
satoken提供了StpUtill鉴权工具类,其中包含非常多的静态方法可以使用,详情请参考:Sa-Token。
// 会话登录,参数填登录人的账号id
StpUtil.login(10001);
// 校验当前客户端是否已经登录,如果未登录则抛出 `NotLoginException` 异常
StpUtil.checkLogin();
// 将账号id为 10077 的会话踢下线
StpUtil.kickout(10077);
4.2、注解鉴权
尽管StpUtill鉴权工具类已经非常方便但还是有同学钟爱注解鉴权,satoken也是提供了这种方式的。
// 注解鉴权:只有具备 `user:add` 权限的会话才可以进入方法
@SaCheckPermission("user:add")
public String insert(SysUser user) {
// ...
return "用户增加";
}
注解鉴权默认是关闭的,要使用的话需要将satoken全局拦截器注入到项目中!
@Configuration
public class SecurityConfiguration implements WebMvcConfigurer {
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
如上就注册了satoken的全局拦截器,就可以愉快的使用注解权限了。官方文档:Sa-Token。
注意:这里配置的是“/**”
,也就是全路径,所有后面要在不需要鉴权的接口上加上@SaIgnore
用于忽略鉴权。
如下配置了/auth
开头的接口不用鉴权
@RestController
@RequiredArgsConstructor
@Validated
@RequestMapping("/auth")
@SaIgnore
public class AuthController {
private final AuthService authService;
/**
* 登录
*
* @param reqVO 登录请求
* @return token
*/
@PostMapping("/login")
public CommonResult<LoginRespVO> login(@RequestBody @Valid LoginReqVO reqVO) {
return success(authService.login(reqVO));
}
...
}
如下是一个简单的需要鉴权的示例
@RestController
@RequestMapping("/system/role")
@RequiredArgsConstructor
public class RoleController {
private final RoleService roleService;
/**
* 创建角色
*
* @param reqVO 角色信息
* @return id
*/
@PostMapping("/create")
@SaCheckPermission("system:role:create")
public CommonResult<Long> createRole(@Valid @RequestBody RoleCreateReqVO reqVO) {
return success(roleService.createRole(reqVO));
}
/**
* 更新角色
*
* @param reqVO 角色信息
* @return 结果
*/
@PutMapping("/update")
@SaCheckPermission("system:role:update")
public CommonResult<Boolean> updateRole(@Valid @RequestBody RoleUpdateReqVO reqVO) {
roleService.updateRole(reqVO);
return success(true);
}
...
}
5、全局异常
通过定义全局异常拦截器可以返回前端统一的格式,以下仅供参考。
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 权限码异常
*/
@ExceptionHandler(NotPermissionException.class)
public CommonResult<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
String requestUri = request.getRequestURI();
log.error("请求地址'{}',权限码校验失败'{}'", requestUri, e.getMessage());
return CommonResult.error(FORBIDDEN);
}
/**
* 角色权限异常
*/
@ExceptionHandler(NotRoleException.class)
public CommonResult<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
String requestUri = request.getRequestURI();
log.error("请求地址'{}',角色权限校验失败'{}'", requestUri, e.getMessage());
return CommonResult.error(FORBIDDEN);
}
/**
* 认证失败
*/
@ExceptionHandler(NotLoginException.class)
public CommonResult<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
String requestUri = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestUri, e.getMessage());
return CommonResult.error(UNAUTHORIZED);
}
/**
* 无效认证
*/
@ExceptionHandler(SameTokenInvalidException.class)
public CommonResult<Void> handleSameTokenInvalidException(SameTokenInvalidException e, HttpServletRequest request) {
String requestUri = request.getRequestURI();
log.error("请求地址'{}',内网认证失败'{}',无法访问系统资源", requestUri, e.getMessage());
return CommonResult.error(UNAUTHORIZED);
}
...
}
6、跨域(可选)
官网也有提供解决方法Sa-Token,我非常建议你看完这篇文章后再决定使用什么方式Sa-Token。
看个人选择吧,我这里使用的是satoken拦截器+普通corsFilter的方式。
@Configuration
public class OkayWebAutoConfiguration implements WebMvcConfigurer {
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}
7、启动类
启动类确保以上需要的组件被扫描注册就好。
@SpringBootApplication
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
8、启动测试
这里我使用了redis,可以先忽略。
因为我的前端是一整个项目,不好拆出来贴代码,所以直接展示登录结果了!
尝试登录
发起一个创建请求,因为前端项目已经配置了发请求的header,所以请求就会带上前面的token了。
然后satoken拦截器发挥作用进行鉴权,关于拦截器如何鉴权的,我之后还会再讲的。这次先到这吧。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)