前言
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。 还有踢人下线、账号封禁、路由拦截规则、微服务网关鉴权、密码加密等丰富功能 它不比 Shiro 和 SpringSecurity 的功能少,而且配置使用更加简单
一、引入和配置 先给你们看一下 Demo 文件结构
1.引入依赖
如果不需要将 token 信息存入 redis,只需要引入下面这一个依赖
1 2 3 4 5 <dependency > <groupId > cn.dev33</groupId > <artifactId > sa-token-spring-boot-starter</artifactId > <version > 1.31.0</version > </dependency >
https://qiniuoss.xuyijie.icu/XuYijieBlog/BlogImage/SaToken.png 使用redis ,无需任何其他配置,只需要多引入下面几个依赖,然后下面的 yml 加一些配置,satoken 就可以自动存储到 redis,非常方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > cn.dev33</groupId > <artifactId > sa-token-dao-redis-jackson</artifactId > <version > 1.31.0</version > </dependency > <dependency > <groupId > cn.dev33</groupId > <artifactId > sa-token-alone-redis</artifactId > <version > 1.31.0</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > </dependency >
2、配置yml
如下代码,如果不需要使用 redis ,则删除 alone-redis
和spring redis
配置,否则连接不到 redis 会报错
如果使用了 redis,我下面的配置是业务和鉴权分离的方式,也就是说,token 存储在 alone-redis
里面配置的数据库,我这里配置的是 0
号数据库,它和 spring reids
配置的数据库不冲突
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 47 48 49 server: port: 8081 sa-token: token-name: satoken timeout: 2592000 activity-timeout: -1 is-concurrent: true is-share: false token-style: uuid is-log: true alone-redis: database: 0 host: 127.0 .0 .1 port: 6379 password: timeout: 10s spring: redis: database: 1 host: 127.0 .0 .1 port: 6379 password: timeout: 10s
3、配置全局异常处理
这一步可以不配置,配置的作用是,在鉴权失败的时候,不会报错,而是返回给前端鉴权失败的原因,方便我们开发调试
下面的异常会在鉴权失败的时候自动返回到前端,无需我们手动抛出和返回
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 package pers.xuyijie.satokendemo.exception;import cn.dev33.satoken.util.SaResult;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public SaResult handlerException (Exception e) { e.printStackTrace(); return SaResult.error(e.getMessage()); } }
4、模拟用户角色和权限
这里我们给用户分配一下我们模拟的角色和权限,正常你们要从数据库读取用户的角色和拥有的权限
这里实现了 StpInterface 下面的方法,下面的方法会在接口鉴权之前自动调用,判断角色和权限,无需我们手动调用
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 47 48 49 50 package pers.xuyijie.satokendemo.permission;import cn.dev33.satoken.stp.StpInterface;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;@Component public class UserPermission implements StpInterface { @Override public List<String> getPermissionList (Object loginId, String loginType) { List<String> list = new ArrayList <>(); list.add("1" ); list.add("user-add" ); list.add("user-delete" ); list.add("user-update" ); list.add("user-get" ); list.add("article-get" ); System.out.println("用户权限列表:" + list); return list; } @Override public List<String> getRoleList (Object loginId, String loginType) { List<String> list = new ArrayList <>(); list.add("user" ); list.add("admin" ); list.add("super-admin" ); System.out.println("用户角色列表:" + list); return list; } }
5、配置拦截器
如果在高版本 SpringBoot (≥2.6.x) 下注册拦截器失效,则需要添加 @EnableWebMvc 注解才可以使用
下面我们配置的规则叫作路由拦截规则
,/user/**
意思就是接口地址为 /user
开头的所有接口,也就是说,下面的我们 UserController
里面的所有接口都在拦截范围内
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 47 48 49 package pers.xuyijie.satokendemo.config;import cn.dev33.satoken.config.SaTokenConfig;import cn.dev33.satoken.interceptor.SaInterceptor;import cn.dev33.satoken.router.SaRouter;import cn.dev33.satoken.stp.StpUtil;import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Primary;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@SpringBootConfiguration @EnableWebMvc public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor (handler -> { SaRouter.match("/**" , "/user/doLogin" , r -> StpUtil.checkLogin()); SaRouter.match("/admin/**" , r -> StpUtil.checkRoleOr("admin" , "super-admin" )); SaRouter.match("/user/**" , r -> StpUtil.checkRole("user" )); SaRouter.match("/admin/**" , r -> StpUtil.checkPermission("admin" )); SaRouter.match("/**" , r -> System.out.println("--------权限认证成功-------" )); }).isAnnotation(true )) .addPathPatterns("/**" ) .excludePathPatterns("/user/doLogin" ); } }
6、controller里调用satoken的方法
方法上面的注解是使用权限认证和拦截器的时候用的,下面我会讲到
我在下面的UserController
演示了登录
、注销
、检查是否登录
、查看用户token
、获取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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 package pers.xuyijie.satokendemo.controller;import cn.dev33.satoken.annotation.SaIgnore;import cn.dev33.satoken.basic.SaBasicUtil;import cn.dev33.satoken.secure.SaBase64Util;import cn.dev33.satoken.secure.SaSecureUtil;import cn.dev33.satoken.stp.SaLoginModel;import cn.dev33.satoken.stp.SaTokenInfo;import cn.dev33.satoken.stp.StpUtil;import cn.dev33.satoken.util.SaResult;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;@RestController @RequestMapping("/user") public class UserController { private static final String USERNAME = "xyj" ; private static final String PASSWORD = "123456" ; @RequestMapping("/doLogin") public SaResult doLogin (String username, String password) { SaBasicUtil.check("sa:123456" ); if (username.equals(USERNAME) && password.equals(PASSWORD)) { StpUtil.login(1 ); tokenInfo = StpUtil.getTokenInfo(); System.out.println(tokenInfo); return SaResult.ok("登录成功,会话ID为 " + StpUtil.getLoginId() + " ,Token为:" + StpUtil.getTokenValue()); } return SaResult.error("登录失败" ); } @RequestMapping("/signOut") public SaResult signOut () { String loginId = null ; if (StpUtil.isLogin()){ loginId = (String) StpUtil.getLoginId(); StpUtil.logout(); } return SaResult.ok("会话ID为 " + loginId + " 的用户注销登录成功" ); } @RequestMapping("/isLogin") public SaResult isLogin () { if (StpUtil.isLogin()){ return SaResult.ok("会话是否登录:" + StpUtil.isLogin() + " ,会话ID为 " + StpUtil.getLoginId()); } return SaResult.ok("会话是否登录:" + StpUtil.isLogin()); } @RequestMapping("/getUserByToken/{tokenValue}") public SaResult getUserByToken (@PathVariable String tokenValue) { return SaResult.ok((String) StpUtil.getLoginIdByToken(tokenValue)); } @RequestMapping("/getTokenTimeout") public SaResult getTokenTimeout () { return SaResult.ok(String.valueOf(StpUtil.getTokenTimeout())); } @SaIgnore @RequestMapping("/encodePassword") public void encodePassword () throws Exception { String md5 = SaSecureUtil.md5("123456" ); String md5BySalt = SaSecureUtil.md5BySalt("123456" , "salt" ); System.out.println("MD5加密:" + md5); System.out.println("MD5加盐加密:" + md5BySalt); String key = "123456" ; String text = "这是一个明文用于测试AES对称加密" ; String ciphertext = SaSecureUtil.aesEncrypt(key, text); System.out.println("AES加密后:" + ciphertext); String text2 = SaSecureUtil.aesDecrypt(key, ciphertext); System.out.println("AES解密后:" + text2); HashMap<String, String> keyMap = SaSecureUtil.rsaGenerateKeyPair(); String privateKey = keyMap.get("private" ); String publicKey = keyMap.get("public" ); String text1 = "这是一个明文用于测试RSA非对称加密" ; String ciphertext1 = SaSecureUtil.rsaEncryptByPublic(publicKey, text1); System.out.println("公钥加密后:" + ciphertext1); String text3 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext1); System.out.println("私钥解密后:" + text3); String text4 = "这是一个明文用于测试Base64" ; String base64Text = SaBase64Util.encode(text4); System.out.println("Base64编码后:" + base64Text); String text5 = SaBase64Util.decode(base64Text); System.out.println("Base64解码后:" + text5); } }
下面的TestController
里面等下演示权限认证和路由拦截的时候用
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package pers.xuyijie.satokendemo.controller;import cn.dev33.satoken.annotation.*;import cn.dev33.satoken.util.SaResult;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/test") @SaCheckLogin public class TestController { @SaIgnore @RequestMapping("/getList") public SaResult getList () { return SaResult.ok("无需登录接口" ); } @SaCheckLogin @RequestMapping("/select") public SaResult select () { return SaResult.ok("查询成功" ); } @SaCheckRole("super-admin") @RequestMapping("/delete") public SaResult delete () { return SaResult.ok("删除成功" ); } @RequestMapping("/add") @SaCheckPermission(value = {"user-add", "user-all"}, mode = SaMode.OR) public SaResult add () { return SaResult.ok("添加成功" ); } @RequestMapping("/update") @SaCheckPermission(value = "user-add", orRole = "admin") public SaResult update () { return SaResult.ok("更新成功" ); } @RequestMapping("/testPermission") @SaCheckPermission(value = "user123") public SaResult testPermission () { return SaResult.ok("这个接口测试用" ); } }
二、登录演示 到这里,我么前期的配置就已经结束了,下面我开始测试每一个方法,为大家展示运行结果并解析代码,先把项目运行起来
1、登录-doLogin
大家请看,我在请求 /doLogin
这个接口的时候,弹出了下面的认证框,这个就是方法第一行代码的功能,这叫 Basic认证
,当然可以不要这一行代码,随你们,认证账号 sa 密码 123456
1 2 SaBasicUtil.check("sa:123456" );
进行 Basic认证
后,登陆成功
我这里使用了 redis ,token 已经存储进来了
2、验证登录-isLogin
3、获取token时效-getTokenTimeout
这是我们在 yml 里面配置的时效性,30天的毫秒
4、加密
请求 encodePassword
接口
5、注销登录-logout
三、权限认证和拦截器演示
下面演示上面我们配置的拦截器,satoken 可以直接使用注解来进行拦截,很方便
我们可以发现,我在两个 controller 里面使用了 satoken 的几个注解,注解可以用在方法上或类上@SaIgnore
忽略该方法,不进行任何拦截和鉴权@SaCheckLogin
登录后才可以调用该接口@SaCheckRole("super-admin")
登录用户必须要是”super-admin”角色才可调用@SaCheckPermission(value = "del")
登录用户必须要有”del”权限才可调用
1、登录认证
下面我们调用添加了 @SaCheckLogin
注解的方法
(1) 未登录情况
(2) 已登陆情况
可以看到调用成功,控制台打印出我们上面拦截器配置的输出信息
2、权限认证
登录后,我们调用增加了 @SaCheckPermission(value = "del")
注解的方法,可以看到提示无此权限:del
,因为前面我们模拟用户权限时,没有给用户分配 del
权限
我们再调用增加了 @SaCheckRole("super-admin")
注解的方法,可以看到成功
总结