目前我仅以学习和尝鲜为目的来集成,不建议用于公司等正式环境,公司还是建议
Shiro
和
Spring Security
那一套。(等我实战一波看看效果再说)
为什么要尝鲜Sa-Token
之前我还是挺排斥国产小作坊的开源作品,毕竟不是根红苗正,但是随着近几年国内开源社区的大力发展,以及在平时工作中又接触了解很多,慢慢改变了我的看法,其实国人开源作品还是很香的,其Api简单易用,源码和官方文档都是中文的,功能丰富且能满足很多中国式需求,各种QQ、微信交流群活跃度非常高,总之就是极大程度满足中国式需求。
在权限认证框架领域,使用最多的莫过于Shiro
和Spring Security
,但是一天在逛同性交友
网站(github)的时候,赫然发现了Sa-Token其竟然有2K的star数量,看其中文介绍竟然是轻量级Java权限认证框架,看了下其特性和功能点,就唤起了我强烈的好奇心,于是乎就有了今天的尝鲜。
Sa-Token是什么?
sa-token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题
框架针对踢人下线、自动续签、前后台分离、分布式会话……等常见业务进行N多适配,通过sa-token,你可以以一种极简的方式实现系统的权限认证部分
与其它权限认证框架相比,sa-token
具有以下优势:
- 简单 :可零配置启动框架,真正的开箱即用,低成本上手
- 强大 :目前已集成几十项权限相关特性,涵盖了大部分业务场景的解决方案
- 易用 :如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
- 高扩展 :几乎所有组件都提供了扩展接口,90%以上的逻辑都可以按需重写
Sa-Token 能做什么?
- 登录验证 —— 轻松登录鉴权,并提供五种细分场景值
- 权限验证 —— 适配RBAC权限模型,不同角色不同授权
- Session会话 —— 专业的数据缓存中心
- 踢人下线 —— 将违规用户立刻清退下线
- 持久层扩展 —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
- 分布式会话 —— 提供jwt集成和共享数据中心两种分布式会话方案
- 单点登录 —— 一处登录,处处通行
- 模拟他人账号 —— 实时操作任意用户状态数据
- 临时身份切换 —— 将会话身份临时切换为其它账号
- 无Cookie模式 —— APP、小程序等前后台分离场景
- 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- 多账号认证体系 —— 比如一个商城项目的user表和admin表分开鉴权
- 花式token生成 —— 内置六种token风格,还可自定义token生成策略
- 注解式鉴权 —— 优雅的将鉴权与业务代码分离
- 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配restful模式
- 自动续签 —— 提供两种token过期策略,灵活搭配使用,还可自动续签
- 会话治理 —— 提供方便灵活的会话查询接口
- 记住我模式 —— 适配[记住我]模式,重启浏览器免验证
- 密码加密 —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
- 组件自动注入 —— 零配置与Spring等框架集成
快速集成
依赖导入
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.15.2</version>
</dependency>
最新版本去maven中央库自己查询下,当前是1.15.2。
配置文件
你可以零配置启动项目
但同时你也可以在application.yml
中增加如下配置,定制性使用框架:
spring:
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
登录
@PostMapping("/api/v1/login")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "登录")
public Response login(String userName, String pwd) {
log.info("login,username:{},pwd:{}", userName, pwd);
// 模拟 校验用户名密码
Long userId = check(userName,pwd);
StpUtil.setLoginId(userId);
return Response.ok(StpUtil.getTokenInfo());
}
核心就一行StpUtil.setLoginId(userId)
,来看看它帮我们做了什么?
源码及其简单,还有很多中文注释,跟着读就行了,直接贴结论。
- 创建token
- 创建SaSession
- 在session上记录token签名
- 创建token、loginId映射
- token写入cookie
底层会话等存储使用的是Map
源码如下:
/**
* 数据集合
*/
public Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();
/**
* 过期时间集合 (单位: 毫秒) , 记录所有key的到期时间 [注意不是剩余存活时间]
*/
public Map<String, Long> expireMap = new ConcurrentHashMap<String, Long>();
调用结果如下:
- Response Heards
Connection: keep-alive
Content-Type: application/json
Date: Fri, 09 Apr 2021 07:33:59 GMT
Keep-Alive: timeout=60
// 重点
Set-Cookie: LakerToken=da14afd3f4b648a889a1e51ac3ec53d7; Max-Age=1800; Expires=Fri, 09-Apr-2021 08:03:59 GMT; Path=/
Transfer-Encoding: chunked
- Response Body
{
"code": 200,
"msg": "",
"data": {
"tokenName": "LakerToken",
"tokenValue": "da14afd3f4b648a889a1e51ac3ec53d7",
"isLogin": true,
"loginId": "1",
"loginKey": "login",
"tokenTimeout": 1784,
"sessionTimeout": 1784,
"tokenSessionTimeout": -2,
"tokenActivityTimeout": 30,
"loginDevice": "default-device"
}
}
可以看到返回heards中已自动设置:
Set-Cookie: LakerToken=da14afd3f4b648a889a1e51ac3ec53d7; Max-Age=1800; Expires=Fri, 09-Apr-2021 08:03:59 GMT; Path=/
登出
@PostMapping("/api/v1/loginOut")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "登出")
@SaCheckLogin
public Response loginOut() {
StpUtil.logout();
return Response.ok();
}
核心也是一行StpUtil.logout()
,来看看它帮我们做了什么?
- 获取
HttpRequest
- 尝试从request里读取token
- 尝试从请求体里面读取token
- 尝试从header里读取token
- 尝试从cookie里读取token
- 删除cookie
- 删除token、loginId映射
- 注销session
请求拦截鉴权
第一步:配置全局拦截器
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
/**
* 注册sa-token的拦截器,打开注解式鉴权功能 (如果您不需要此功能,可以删除此类)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
}
第二步:在需要拦截的类或者方法上加注解
@SaCheckLogin
: 标注在方法或类上,当前会话必须处于登录状态才可通过校验@SaCheckRole("admin")
: 标注在方法或类上,当前会话必须具有指定角色标识才能通过校验@SaCheckPermission("user:add")
: 标注在方法或类上,当前会话必须具有指定权限才能通过校验
例如:
@GetMapping("/api/v1/tokenInfo")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "获取当前会话的token信息")
@SaCheckLogin
public Response tokenInfo() {
return Response.ok(StpUtil.getTokenInfo());
}
加上
@SaCheckLogin
则该接口必须处于登录状态才可通过校验。
这里核心拦截校验又是如何工作的呢?
可以看下SaAnnotationInterceptor.java
源码,基于SpringMvc的拦截器实现的拦截校验。
实现功能如下:
- 验证登录
- 验证角色
- 验证权限
实现流程原理如下:
- 获取HttpRequest中的token
- 尝试从request里读取token
- 尝试从请求体里面读取token
- 尝试从header里读取token
- 尝试从cookie里读取token
- 判断token
- 无效token
- 过期
- 被顶下线
- 被踢下线
- 自动续期
权限和角色扩展
直接实现StpInterface
接口,覆写getPermissionList
和getRoleList
方法即可。
@Component
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginKey) {
xxx
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
xxx
}
}
集群环境
Sa-token默认将会话数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:重启后数据会丢失,无法在集群模式下共享数据。
为此,sa-token将数据持久操作全部抽象到 SaTokenDao
接口中,此设计可以保证开发者对框架进行灵活扩展,比如我们可以将会话数据存储在 Redis
、Memcached
等专业的缓存中间件中,做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性。
除了框架内部对SaTokenDao
提供的基于内存的默认实现,我们使用官网提供的Redis扩展。
依赖导入
<!-- sa-token整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.15.2</version>
</dependency>
<!-- 提供redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
使用Jackson序列化方式,Session序列化后可读性强,可灵活手动修改
配置文件
spring:
# redis配置
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间(毫秒)
timeout: 1000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
引入依赖和配置后,框架会自动使用Redis存储。
总结
初步尝试还挺不错的,文档和代码示例都很全,基本功能都能满足,源码简单易懂,可以随意二开,封装度非常高,不理解原理的就很容易变成工具人了,其他的等用一段时间再评论。
参考:
-
https://github.com/dromara/sa-token
加群一起抱团取暖,共同进步
🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎
🍎个人vx【lakernote】