个人博客:无奈何杨(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的配置文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| sa-token: token-name: Authorization timeout: 3600 active-timeout: 1800 is-concurrent: false is-share: true is-read-header: true is-read-cookie: false token-prefix: "Bearer" token-style: tik is-log: true
|
3、自定义权限认证
官方文档:因为每个项目的需求不同,其权限设计也千变万化,因此 [
获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token
将此操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。
所以根据自己的不同业务设计需要的权限认证方法就好。下面是我项目中的写法,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Setter public class StpInterfaceImpl implements StpInterface {
private LoginService loginService;
@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<>(); }
@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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @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。
1 2 3 4 5 6
| StpUtil.login(10001);
StpUtil.checkLogin();
StpUtil.kickout(10077);
|
4.2、注解鉴权
尽管StpUtill鉴权工具类已经非常方便但还是有同学钟爱注解鉴权,satoken也是提供了这种方式的。
1 2 3 4 5 6
| @SaCheckPermission("user:add") public String insert(SysUser user) { return "用户增加"; }
|
注解鉴权默认是关闭的,要使用的话需要将satoken全局拦截器注入到项目中!
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration public class SecurityConfiguration implements WebMvcConfigurer {
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); } }
|
如上就注册了satoken的全局拦截器,就可以愉快的使用注解权限了。官方文档:Sa-Token。
注意:这里配置的是“/**”
,也就是全路径,所有后面要在不需要鉴权的接口上加上@SaIgnore
用于忽略鉴权。
如下配置了/auth
开头的接口不用鉴权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @RestController @RequiredArgsConstructor @Validated @RequestMapping("/auth") @SaIgnore public class AuthController {
private final AuthService authService;
@PostMapping("/login") public CommonResult<LoginRespVO> login(@RequestBody @Valid LoginReqVO reqVO) { return success(authService.login(reqVO)); }
... }
|
如下是一个简单的需要鉴权的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @RestController @RequestMapping("/system/role") @RequiredArgsConstructor public class RoleController {
private final RoleService roleService;
@PostMapping("/create") @SaCheckPermission("system:role:create") public CommonResult<Long> createRole(@Valid @RequestBody RoleCreateReqVO reqVO) { return success(roleService.createRole(reqVO)); }
@PutMapping("/update") @SaCheckPermission("system:role:update") public CommonResult<Boolean> updateRole(@Valid @RequestBody RoleUpdateReqVO reqVO) { roleService.updateRole(reqVO); return success(true); }
... }
|
5、全局异常
通过定义全局异常拦截器可以返回前端统一的格式,以下仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @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的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration public class OkayWebAutoConfiguration implements WebMvcConfigurer {
@Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setMaxAge(1800L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
}
|
7、启动类
启动类确保以上需要的组件被扫描注册就好。
1 2 3 4 5 6 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