一、介绍
官网:
Sa-Tokenhttps://sa-token.cc/index.html
特性:
- 登录与权限认证:支持用户登录和细粒度权限认证。
- 会话管理:提供会话创建、维护和销毁功能。
- 单点登录:支持单点登录,简化多应用登录流程。
- OAuth2.0集成:集成OAuth2.0协议,实现安全授权与认证。
- 微服务网关鉴权:确保微服务间的安全访问。
- 轻量且易用:功能强大但易于集成和使用。
登录流程简介:
对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:
- 如果校验通过,则:正常返回数据。
- 如果校验未通过,则:抛出异常,告知其需要先进行登录。
- 用户提交
name
+password
参数,调用登录接口。- 登录成功,返回这个用户的 Token 会话凭证。
- 用户后续的每次请求,都携带上这个 Token。
- 服务器根据 Token 判断此会话是否登录成功。
所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续判断会话是否登录的关键所在。
二、集成
注:如果你使用的是 SpringBoot 3.x,只需要将
sa-token-spring-boot-starter
修改为sa-token-spring-boot3-starter
即可。
1-添加依赖
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
2-配置文件编写
若依sa-token 配置在yml中,如下:ruoyi-admin/src/main/resources/application.yml
属性讲解:
- timeout: 86400 固定有效期,必定一天过期
- active-timeout: 1800 临时有效期,30分钟误操作token过期
is-concurrent:并发登录,为true时允许一起登录, 为false时新登录挤掉旧登录 is-read-cookie 是否尝试从cookie里读取token,默认不从cookie中获取,可能会有csrf攻击
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期 设为一天 (必定过期) 单位: 秒
timeout: 86400
# 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
# token最低活跃时间 (指定时间无操作就过期) 单位: 秒
active-timeout: 1800
# 允许动态设置 token 有效期
dynamic-active-timeout: true
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# 是否尝试从header里读取token
is-read-header: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
三、 常用函数
1-登录、注销函数
StpUtil.login(Object id); | 只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于: 1-检查此账号是否之前已有登录; 你暂时不需要完整了解整个登录过程,你只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端。 流程与思路详见:
|
StpUtil.logout(); | // 当前会话注销登录 |
| // 获取当前会话是否已经登录,返回true=已登录,false=未登录 |
StpUtil.checkLogin(); | // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException` |
2-强制注销
StpUtil.logout(10001); // 强制指定账号注销下线
StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线
StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线
3-踢人下线
StpUtil.kickout(10001); // 将指定账号踢下线
StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线
StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线
强制注销 和 踢人下线 的区别在于:
- 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。
- 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
4-会话函数
StpUtil.getLoginId(); | // 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException` |
类似上面 | StpUtil.getLoginIdAsString(); // 获取当前会话账号id, 并转化为`String`类型 StpUtil.getLoginIdAsInt(); // 获取当前会话账号id, 并转化为`int`类型 StpUtil.getLoginIdAsLong(); // 获取当前会话账号id, 并转化为`long`类型 |
StpUtil.getLoginIdDefaultNull(); | / 获取当前会话账号id, 如果未登录,则返回 null |
StpUtil.getLoginId(T defaultValue); | // 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型) |
5-Token查询
StpUtil.getTokenValue(); | // 获取当前会话的 token 值 |
StpUtil.getTokenName(); | // 获取当前`StpLogic`的 token 名称 |
StpUtil.getLoginIdByToken(String tokenValue); | // 获取指定 token 对应的账号id,如果未登录,则返回 null |
StpUtil.getTokenTimeout(); | // 获取当前会话剩余有效期(单位:s,返回-1代表永久有效) |
StpUtil.getTokenInfo(); | // 获取当前会话的 token 信息参数 |
关于token参数详解:
{
"code": 200,
"msg": "ok",
"data": {
"tokenName": "satoken", // token名称
"tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值
"isLogin": true, // 此token是否已经登录
"loginId": "10001", // 此token对应的LoginId,未登录时为null
"loginType": "login", // 账号类型标识
"tokenTimeout": 2591977, // token剩余有效期 (单位: 秒)
"sessionTimeout": 2591977, // Account-Session剩余有效时间 (单位: 秒)
"tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
"tokenActiveTimeout": -1, // token 距离被冻结还剩的时间 (单位: 秒)
"loginDevice": "default-device" // 登录设备类型
},
}
四、权限
自行查看 api接口
权限认证 (sa-token.cc)https://sa-token.cc/doc.html?code=25446615d46b82ca1f92853e66d5d64c63fe3a34e6a611a52458847155bff768#/use/jur-auth
5-注解鉴权
注解鉴权 —— 优雅的将鉴权与业务代码分离!
@SaCheckLogin
: 登录校验 —— 只有登录之后才能进入该方法。@SaCheckRole("admin")
: 角色校验 —— 必须具有指定角色标识才能进入该方法。@SaCheckPermission("user:add")
: 权限校验 —— 必须具有指定权限才能进入该方法。@SaCheckSafe
: 二级认证校验 —— 必须二级认证之后才能进入该方法。@SaCheckBasic
: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。@SaIgnore
:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。@SaCheckDisable("comment")
:账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。
你必须手动将 Sa-Token 的全局拦截器注册到你项目中
官网文档:
路由拦截鉴权 (sa-token.cc)https://sa-token.cc/doc.html?code=25446615d46b82ca1f92853e66d5d64c63fe3a34e6a611a52458847155bff768#/use/route-check?id=_1%E3%80%81%E6%B3%A8%E5%86%8C-sa-token-%E8%B7%AF%E7%94%B1%E6%8B%A6%E6%88%AA%E5%99%A8
下面是若依中的实现:
@RequiredArgsConstructor
@Slf4j
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
private final SecurityProperties securityProperties;
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaInterceptor(handler -> {
AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
// 登录验证 -- 排除多个路径
SaRouter
// 获取所有的
.match(allUrlHandler.getUrls())
// 对未排除的路径进行检查
.check(() -> {
// 检查是否登录 是否有token
StpUtil.checkLogin();
// 有效率影响 用于临时测试
// if (log.isDebugEnabled()) {
// log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
// log.info("临时有效时间: {}", StpUtil.getTokenActiveTimeout());
// }
});
})).addPathPatterns("/**")
// 排除不需要拦截的路径
.excludePathPatterns(securityProperties.getExcludes());
}
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface() {
return new SaPermissionImpl();
}
/**
* 自定义dao层存储
*/
@Bean
public SaTokenDao saTokenDao() {
return new PlusSaTokenDao();
}
}
五、session 会话
在 Sa-Token 中,Session 分为三种,分别是:
Account-Session
: 指的是框架为每个 账号id 分配的 SessionToken-Session
: 指的是框架为每个 token 分配的 SessionCustom-Session
: 指的是以一个 特定的值 作为SessionId,来分配的 Session
// 在登录时缓存 user 对象
StpUtil.getSession().set("user", user);
// 然后我们就可以在任意处使用这个 user 对象
SysUser user = (SysUser) StpUtil.getSession().get("user");
1-Account-Session 绑定当前用户id ,api如下
// 获取当前账号 id 的 Account-Session (必须是登录后才能调用)
StpUtil.getSession();
// 获取当前账号 id 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSession(true);
// 获取账号 id 为 10001 的 Account-Session
StpUtil.getSessionByLoginId(10001);
// 获取账号 id 为 10001 的 Account-Session, 并决定在 Session 尚未创建时,是否新建并返回
StpUtil.getSessionByLoginId(10001, true);
// 获取 SessionId 为 xxxx-xxxx 的 Account-Session, 在 Session 尚未创建时, 返回 null
StpUtil.getSessionBySessionId("xxxx-xxxx");
2- Token-Session 绑定当前token的信息
// 获取当前 Token 的 Token-Session 对象
StpUtil.getTokenSession();
// 获取指定 Token 的 Token-Session 对象
StpUtil.getTokenSessionByToken(token);
3-Custom-Session 自定义sessionid
比如 ,下面goods-10001是某个商品的id
// 查询指定key的Session是否存在
SaSessionCustomUtil.isExists("goods-10001");
// 获取指定key的Session,如果没有,则新建并返回
SaSessionCustomUtil.getSessionById("goods-10001");
// 获取指定key的Session,如果没有,第二个参数决定是否新建并返回
SaSessionCustomUtil.getSessionById("goods-10001", false);
// 删除指定key的Session
SaSessionCustomUtil.deleteSessionById("goods-10001");
官网还提供一些 session 读写的api ,自行查看
Session会话 (sa-token.cc)https://sa-token.cc/doc.html?code=25446615d46b82ca1f92853e66d5d64c63fe3a34e6a611a52458847155bff768#/use/session?id=%E5%9C%A8-session-%E4%B8%8A%E5%AD%98%E5%8F%96%E5%80%BC
六、sa-token 登录拦截器
1、路由权限拦截器
类路径:com.ruoyi.framework.config.SaTokenConfig,实现WebMvcConfigurer 接口
1-通过 registry.addInterceptor(new SaInterceptor(handler -> { } 添加 SaInterceptor 拦截器2-在拦截器中可以自定义登录、角色、权限的校验
- 登录校验 -- 拦截所有路由,并排除/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"));
3- 排除不需要拦截的路径(该配置在 ruoyi-admin 模块的yml中配置)
.excludePathPatterns(securityProperties.getExcludes());其他注入
- 通过 getStpLogicJwt 方法, 注入 tpLogic 类是为了整合Jwt
- 通过 stpInterface方法,注入 StpInterface 接口,手动去实现
注入SaTokenDao ,实现对redis对sotoken的读写
@RequiredArgsConstructor
@Slf4j
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
private final SecurityProperties securityProperties;
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaInterceptor(handler -> {
AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
// 登录验证 -- 排除多个路径
SaRouter
// 获取所有的
.match(allUrlHandler.getUrls())
// 对未排除的路径进行检查
.check(() -> {
// 检查是否登录 是否有token
StpUtil.checkLogin();
// 有效率影响 用于临时测试
// if (log.isDebugEnabled()) {
// log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
// log.info("临时有效时间: {}", StpUtil.getTokenActiveTimeout());
// }
});
})).addPathPatterns("/**")
// 排除不需要拦截的路径
.excludePathPatterns(securityProperties.getExcludes());
}
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface() {
return new SaPermissionImpl();
}
/**
* 自定义dao层存储
* PlusSaTokenDao Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
*/
@Bean
public SaTokenDao saTokenDao() {
return new PlusSaTokenDao();
}
}
2- saTokenDao
上面配置中,自定义dao层存储,注入 PlusSaTokenDao 作为 Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一) ,
@Bean
public SaTokenDao saTokenDao() {
return new PlusSaTokenDao();
}
若果不去自己实现sotoken的DAO层接口,框架默认会基于内存区存储,如下 SaTokenDaoDefaultImpl 类实现 SaTokenDao
public class SaTokenDaoDefaultImpl implements SaTokenDao {
public Map<String, Object> dataMap = new ConcurrentHashMap();
public Map<String, Long> expireMap = new ConcurrentHashMap();
.......
3-sotoken Jwt 介绍
JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的详细博文如下:
JWT详解-CSDN博客文章浏览阅读10w+次,点赞927次,收藏4.6k次。本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:JWT详解JWT简介1.什么是JWT在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:客户端使用用户名和密码请求登录服务端收到请求,验证用户名和密码验证成功后,服务端会签发一个token,再把这个token返回给客户端客户端收到token后可以把它存储起来,比如放到cookie中客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带服务端收到请求,然后去验证客户端请_jwt详解https://blog.csdn.net/weixin_45070175/article/details/118559272
1-集成
依赖:ruoyi-common/pom.xml 中
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
</dependency>
配置秘钥 : 上面 ruoyi-amin配置文件sotoken中配置
# Sa-Token配置
sa-token:
# jwt秘钥
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
com.ruoyi.framework.config.SaTokenConfig 类中注入
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
整合文档参考:
和 jwt 集成 (sa-token.cc)https://sa-token.cc/doc.html?code=25446615d46b82ca1f92853e66d5d64c63fe3a34e6a611a52458847155bff768#/plugin/jwt-extend