SpringBoot集成Sa-Token进行登录鉴权
SpringBoot集成Sa-token
Sa-Token是轻量级的认证框架,主要解决:登录认证,权限认证、单点登录、微服务网关鉴权,就不用自己手写认证过程,非常方便,像权限菜单,权限按钮Sa-Token都可以帮助我们去实现
Sa-Token文档:链接
Sa-Token开源代码:链接
1.1、基础导入
依赖导入
<!-- SpringBoot2.x -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.44.0</version>
</dependency>
<!-- SpringBoot3.x -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.44.0</version>
</dependency>
application.yml
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# 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
1.2、简易登录操作(单体架构)
controller层
这个可能有点复杂了,因为我直接用微服务中的登录代码,稍微解读一下
@PostMapping("/login")
public Result systemUserLogin(@RequestBody LoginDTO loginDTO) {
// 在微服务中,在认证服务使用了OpenFeign远程调用login这个接口,如果在单体架构中,直接调用自己的查询用户接口就可以了,因为登录步骤一般都是:
// 1、输入账户和密码
// 2、根据账户判断用户是否存在
// 3、存在就进行一系列的基础校验(检查完后,将用户信息返回给Controller层)
// 稍微改造一下就是SystemUser systemUser = cloudSystemUserAPI.login(loginDTO.getUserName(), loginDTO.getPassword());这是单体架构中的一个示例,并不是当前的标准写法
Result result = cloudSystemUserAPI.login(loginDTO.getUserName(), loginDTO.getPassword());
// 获取返回的data数据,获取data中的id进行sa-token权限设置(这一段可以不看,微服务中的)
LinkedHashMap<String, Object> data = (LinkedHashMap<String, Object>) result.getData();
if (data == null) {
String msg = result.getMsg();
ResultCodeEnum code = getByMessage(msg);
return Result.build(null, code);
}
// 直接看这里,我们根据改造后的写法就是Integer id = systemUser.getId();
// 然后直接StpUtil.login(id);就可以了,然后sa-token就会返回如下格式:
// 这时可以观察cookie中会携带tokenName,然后在其他操作中进行注解鉴权就可以了@SaCheckLogin,详见说明文档中的注解鉴权
Integer id = (Integer) data.get("id");
StpUtil.login(id);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.build(null, SUCCESS, tokenInfo);
}
返回格式:
"saTokenInfo": {
"tokenName": "satoken", // token-name
"tokenValue": "23cd50f1-6cfd-45c2-afde-6970265ac36f", // tokenValue
"isLogin": true, // 是否登录
"loginId": "1", // 登录的用户id
"loginType": "login", // 登录类型
"tokenTimeout": 55,
"sessionTimeout": 55,
"tokenSessionTimeout": -2,
"tokenActiveTimeout": -1,
"loginDeviceType": "DEF",
"tag": null
}

1.3、多用户登录操作(微服务)
这个我自己也琢磨了很久,因为是第一次用这个框架进行多用户鉴权操作,踩坑很多,所以教大家如何避坑,如何准确去操作,有点复杂,但是又不复杂
微服务架构:
网关服务(进行统一鉴权):cloud-gateway-service
认证服务(进行登录操作,派发Token):cloud-auth-service
用户服务(系统用户,普通用户):cloud-user-service
公共服务(存放一些公共配置,远程接口,自定义DTO等):cloud-common-service
我的项目的依赖关系是:所有请求通过网关服务进行转发到其他服务中(除了公共服务),而其他服务依赖于公共服务
登录流程图
这里面集成了Redis,无伤大雅

关键代码
网关服务
config层
@Configuration
// 必须配置,参考文档中的多用户认证
public class SaTokenConfig {
// 注册sa-token全局过滤器
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 开放地址
.addExclude("/favicon.ico")
.addExclude("/swagger-ui/**") // 开放Swagger文档
.addExclude("/v3/api-docs/**") // 开放OpenAPI文档
.addExclude("/auth-service/login") // 开放认证服务中的登录接口
.addExclude("/user-service/user") // 开发用户服务中的根据用户名进行查询
.addExclude("/auth-service/common-user/login") // 开放认证服务中的普通用户登录接口
.addExclude("/common-service/common-user/query") // 开发用户服务中的根据普通用户名进行查询
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验(系统用户)
SaRouter.match("/user-service/**").check(StpUtil::checkLogin);
SaRouter.match("/post-service/**").check(StpUtil::checkLogin);
// 登录校验(普通用户)
SaRouter.match("/common-service/**").check(StpUserUtil::checkLogin);
}).setError(e -> {
return SaResult.error(e.getMessage());
});
}
}
// 这里我就没截全了,因为很多,可以参考代码:https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java
// 这个代码主要就是普通用户登录,因为系统用户登录Type默认是login,所以不需要管,只需要设置普通用户就可以了,文档中提供了两种方式:一种就是对原生的StpUtil进行改造,另一种就是Kit模式
@Component
public class StpUserUtil {
/**
* 多账号体系下的类型标识
*/
public static final String TYPE = "user";
/**
* 底层使用的 StpLogic 对象,这里源代码中没做修改,
但是这里必须做修改,因为在多用户认证中,使用的tokenName默认为satoken,
如果系统用户登录之后,普通用户登录的话,会把系统用户挤下线,所以这是我们需要重写方法,可以参考文档:多账户认证 -> 同端多登录,这是我的踩坑地方
*/
public static StpLogic stpLogic = new StpLogic(TYPE) {
@Override
public String splicingKeyTokenName() {
return super.splicingKeyTokenName() + "-user";
}
};
...
}
application.yml配置
spring:
data:
redis:
port: 6379
host: 192.168.10.105
lettuce:
pool:
max-active: 200
max-wait: -1ms
max-idle: 10
min-idle: 0
application:
name: cloud-gateway-service
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins:
- "http://localhost:5173"
allowed-methods:
- GET
- PUT
- POST
- DELETE
- OPTIONS
allowed-headers:
- "Content-Type"
- "Authorization"
- "X-Requested-With"
allow-credentials: true
max-age: 7200
routes:
# 岗位服务
- id: cloud-post-service # 路由id,没有固定规则,但唯一建议与服务名对应
uri: lb://cloud-post-service # 此处不需要配置uri,因为此处是转发给注册中心,由注册中心进行转发
predicates: # 以下是断言条件,必选全部符合条件
- Path=/post-service/**
# - Method=POST 表示只有post请求才会进行路由转发,但是建议不这么做
# 用户服务(系统用户和普通用户)关键点,不要将系统用户和普通用户的路径统一写user-service,分开写,
# 一个为user-service,一个为common-service,但是都指向同一个服务,这是我踩坑的地方,
# 如果写一样,那么出现的问题就是只登录系统用户没问题,但是只登录普通用户就出现普通用户操作读取不到token,只有登录系统用户才会读取到token
- id: cloud-user-service
uri: lb://cloud-user-service
predicates:
- Path=/user-service/**
- id: cloud-common-service
uri: lb://cloud-user-service
predicates:
- Path=/common-service/**
# 认证服务
- id: cloud-auth-service
uri: lb://cloud-auth-service
predicates:
- Path=/auth-service/**
nacos:
discovery:
server-addr: 192.168.10.105:8848
username: nacos
password: nacos
namespace: public #发布服务到指定的 命名空间,默认为public
group: DEFAULT_GROUP #发布服务到指定的 group ,默认为DEFAULT_GROUP
server:
port: 9090
sa-token:
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 60
# 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
认证服务
/**
* 系统用户登录,这个就根据个人的代码来更改,因为我是自定义了一个响应结构体,思路不变就可以了
*/
@PostMapping("/login")
public Result systemUserLogin(@RequestBody LoginDTO loginDTO) {
Result result = cloudSystemUserAPI.login(loginDTO.getUserName(), loginDTO.getPassword());
// 获取返回的data数据,获取data中的id进行sa-token权限设置
LinkedHashMap<String, Object> data = (LinkedHashMap<String, Object>) result.getData();
if (data == null) {
String msg = result.getMsg();
ResultCodeEnum code = getByMessage(msg);
return Result.build(null, code);
}
Integer id = (Integer) data.get("id");
StpUtil.login(id);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Result.build(null, SUCCESS, tokenInfo);
}
/**
* 普通用户登录
*/
@PostMapping("/common-user/login")
public Result commonUserLogin(@RequestBody LoginDTO loginDTO) {
Result result = cloudSystemUserAPI.queryCommonUserByName(loginDTO.getCommonName(), loginDTO.getPassword());
// 获取返回的data数据,获取data中的id进行sa-token权限设置
LinkedHashMap<String, Object> data = (LinkedHashMap<String, Object>) result.getData();
if (data == null) {
String msg = result.getMsg();
ResultCodeEnum code = getByMessage(msg);
return Result.build(null, code);
}
Integer id = (Integer) data.get("id");
StpUserUtil.login(id);
SaTokenInfo tokenInfo = StpUserUtil.getTokenInfo();
return Result.build(null, SUCCESS, tokenInfo);
}
}
用户服务
/**
* 根据员工id查询员工信息
*/
@SaCheckLogin // 关键点,判断用户是否登录,这里检查的是login类型:satoken
@GetMapping("/query/{id}")
public Result querySystemUserById(@PathVariable Integer id) {
SystemUser systemUser = systemUserService.queryDataById(id);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
Integer postId = systemUser.getPostId();
Result result = cloudSystemPostAPI.querySystemPostById(postId);
Object data = result.getData();
if (data instanceof LinkedHashMap) {
LinkedHashMap<String, Object> postData = (LinkedHashMap<String, Object>) data;
String postName = (String) postData.get("postName");
systemUser.setPostName(postName);
}
return Result.build(systemUser, ResultCodeEnum.SUCCESS, tokenInfo);
}
/**
* 根据用户id查询用户信息
*/
@SaCheckLogin(type = StpUserUtil.TYPE) // 关键点,这里检查的是user类型:satoken-user
@GetMapping("/common-user/query/{id}")
public Result queryDataById(@PathVariable Integer id) {
CommonUser commonUser = commonUserService.queryDataById(id);
return Result.build(commonUser, ResultCodeEnum.SUCCESS, StpUserUtil.getTokenInfo());
}
1.4、总结
对于单用户登录会发现特别简单,但是对于微服务中,多用户就有点小麻烦了。从给出的代码发现,主要配置就在网关服务那里,只要网关服务鉴权那里搞定,其他都很简单了。代码目前我暂时不想全部给出来,因为我想把项目全部做完后再开源给大家一个小参考。

浙公网安备 33010602011771号