从零搭建开发脚手架 集成认证授权 Sa-Token(尝鲜)

开发 后端
目前我仅以学习和尝鲜为目的来集成,不建议用于公司等正式环境,公司还是建议Shiro和Spring Security那一套。(等我实战一波看看效果再说)

 [[392885]]

本文转载自微信公众号「Java大厂面试官」,作者laker。转载本文请联系Java大厂面试官公众号。

目前我仅以学习和尝鲜为目的来集成,不建议用于公司等正式环境,公司还是建议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等框架集成

快速集成

依赖导入

  1. <dependency> 
  2.     <groupId>cn.dev33</groupId> 
  3.     <artifactId>sa-token-spring-boot-starter</artifactId> 
  4.     <version>1.15.2</version> 
  5. </dependency> 

“最新版本去maven中央库自己查询下,当前是1.15.2。

配置文件

你可以零配置启动项目但同时你也可以在application.yml中增加如下配置,定制性使用框架:

  1. spring:  
  2.     # sa-token配置 
  3.     sa-token:  
  4.         # token名称 (同时也是cookie名称) 
  5.         token-name: satoken 
  6.         # token有效期,单位s 默认30天, -1代表永不过期  
  7.         timeout: 2592000 
  8.         # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 
  9.         activity-timeout: -1 
  10.         # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)  
  11.         allow-concurrent-login: true 
  12.         # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)  
  13.         is-share: false 
  14.         # token风格 
  15.         token-style: uuid 

登录

  1. @PostMapping("/api/v1/login"
  2.     @ApiOperationSupport(order = 1) 
  3.     @ApiOperation(value = "登录"
  4.     public Response login(String userName, String pwd) { 
  5.         log.info("login,username:{},pwd:{}", userName, pwd); 
  6.         // 模拟 校验用户名密码  
  7.         Long userId = check(userName,pwd); 
  8.         StpUtil.setLoginId(userId); 
  9.         return Response.ok(StpUtil.getTokenInfo()); 
  10.     } 

核心就一行StpUtil.setLoginId(userId),来看看它帮我们做了什么?

源码及其简单,还有很多中文注释,跟着读就行了,直接贴结论。

  • 创建token
  • 创建SaSession
  • 在session上记录token签名
  • 创建token、loginId映射
  • token写入cookie

底层会话等存储使用的是Map

源码如下:

  1. /** 
  2.  * 数据集合  
  3.  */ 
  4. public Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>(); 
  5.  
  6. /** 
  7.  * 过期时间集合 (单位: 毫秒) , 记录所有key的到期时间 [注意不是剩余存活时间]  
  8.  */ 
  9. public Map<String, Long> expireMap = new ConcurrentHashMap<String, Long>(); 

调用结果如下:

  • Response Heards
  1. Connection: keep-alive 
  2. Content-Type: application/json 
  3. Date: Fri, 09 Apr 2021 07:33:59 GMT 
  4. Keep-Alive: timeout=60 
  5. // 重点 
  6. Set-Cookie: LakerToken=da14afd3f4b648a889a1e51ac3ec53d7; Max-Age=1800; Expires=Fri, 09-Apr-2021 08:03:59 GMT; Path=/ 
  7. Transfer-Encoding: chunked 
  • Response Body
  1.  "code": 200, 
  2.  "msg"""
  3.  "data": { 
  4.   "tokenName""LakerToken"
  5.   "tokenValue""da14afd3f4b648a889a1e51ac3ec53d7"
  6.   "isLogin"true
  7.   "loginId""1"
  8.   "loginKey""login"
  9.   "tokenTimeout": 1784, 
  10.   "sessionTimeout": 1784, 
  11.   "tokenSessionTimeout": -2, 
  12.   "tokenActivityTimeout": 30, 
  13.   "loginDevice""default-device" 
  14.  } 

可以看到返回heards中已自动设置:Set-Cookie: LakerToken=da14afd3f4b648a889a1e51ac3ec53d7; Max-Age=1800; Expires=Fri, 09-Apr-2021 08:03:59 GMT; Path=/

登出

  1. @PostMapping("/api/v1/loginOut"
  2.  @ApiOperationSupport(order = 3) 
  3.  @ApiOperation(value = "登出"
  4.  @SaCheckLogin 
  5.  public Response loginOut() { 
  6.      StpUtil.logout(); 
  7.      return Response.ok(); 
  8.  } 

核心也是一行StpUtil.logout(),来看看它帮我们做了什么?

  • 获取HttpRequest
  • 尝试从request里读取token
  • 尝试从请求体里面读取token
  • 尝试从header里读取token
  • 尝试从cookie里读取token
  • 删除cookie
  • 删除token、loginId映射
  • 注销session

请求拦截鉴权

第一步:配置全局拦截器

  1. @Configuration 
  2. public class MySaTokenConfig implements WebMvcConfigurer { 
  3.  /** 
  4.   * 注册sa-token的拦截器,打开注解式鉴权功能 (如果您不需要此功能,可以删除此类)  
  5.   */ 
  6.     @Override 
  7.     public void addInterceptors(InterceptorRegistry registry) { 
  8.         registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");  
  9.     } 

第二步:在需要拦截的类或者方法上加注解

  • @SaCheckLogin: 标注在方法或类上,当前会话必须处于登录状态才可通过校验
  • @SaCheckRole("admin"): 标注在方法或类上,当前会话必须具有指定角色标识才能通过校验
  • @SaCheckPermission("user:add"): 标注在方法或类上,当前会话必须具有指定权限才能通过校验

例如:

  1. @GetMapping("/api/v1/tokenInfo"
  2.    @ApiOperationSupport(order = 2) 
  3.    @ApiOperation(value = "获取当前会话的token信息"
  4.    @SaCheckLogin 
  5.    public Response tokenInfo() { 
  6.        return Response.ok(StpUtil.getTokenInfo()); 
  7.    } 

加上@SaCheckLogin则该接口必须处于登录状态才可通过校验。

这里核心拦截校验又是如何工作的呢?可以看下SaAnnotationInterceptor.java源码,基于SpringMvc的拦截器实现的拦截校验。

实现功能如下:

  • 验证登录
  • 验证角色
  • 验证权限

实现流程原理如下:

  • 获取HttpRequest中的token
    • 尝试从request里读取token
    • 尝试从请求体里面读取token
    • 尝试从header里读取token
    • 尝试从cookie里读取token
  • 判断token
    • 无效token
    • 过期
    • 被顶下线
    • 被踢下线
  • 自动续期

权限和角色扩展

直接实现StpInterface接口,覆写getPermissionList和getRoleList方法即可。

  1. @Component 
  2. public class StpInterfaceImpl implements StpInterface { 
  3.     /** 
  4.      * 返回一个账号所拥有的权限码集合 
  5.      */ 
  6.     @Override 
  7.     public List<String> getPermissionList(Object loginId, String loginKey) { 
  8.        xxx 
  9.     } 
  10.     /** 
  11.      * 返回一个账号所拥有的角色标识集合 
  12.      */ 
  13.     @Override 
  14.     public List<String> getRoleList(Object loginId, String loginKey) { 
  15.         xxx 
  16.     } 

集群环境

Sa-token默认将会话数据保存在内存中,此模式读写速度最快,且避免了序列化与反序列化带来的性能消耗,但是此模式也有一些缺点,比如:重启后数据会丢失,无法在集群模式下共享数据。

为此,sa-token将数据持久操作全部抽象到 SaTokenDao 接口中,此设计可以保证开发者对框架进行灵活扩展,比如我们可以将会话数据存储在 Redis、Memcached等专业的缓存中间件中,做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性。

除了框架内部对SaTokenDao提供的基于内存的默认实现,我们使用官网提供的Redis扩展。

依赖导入

  1. <!-- sa-token整合redis (使用jackson序列化方式) --> 
  2. <dependency> 
  3.     <groupId>cn.dev33</groupId> 
  4.     <artifactId>sa-token-dao-redis-jackson</artifactId> 
  5.     <version>1.15.2</version> 
  6. </dependency> 
  7. <!-- 提供redis连接池 --> 
  8. <dependency> 
  9.     <groupId>org.apache.commons</groupId> 
  10.     <artifactId>commons-pool2</artifactId> 
  11. </dependency> 

“使用Jackson序列化方式,Session序列化后可读性强,可灵活手动修改

配置文件

  1. spring:  
  2.     # redis配置  
  3.     redis: 
  4.         # Redis数据库索引(默认为0) 
  5.         database: 0 
  6.         # Redis服务器地址 
  7.         host: 127.0.0.1 
  8.         # Redis服务器连接端口 
  9.         port: 6379 
  10.         # Redis服务器连接密码(默认为空) 
  11.         # password:  
  12.         # 连接超时时间(毫秒) 
  13.         timeout: 1000ms 
  14.         lettuce: 
  15.             pool: 
  16.                 # 连接池最大连接数 
  17.                 max-active: 200 
  18.                 # 连接池最大阻塞等待时间(使用负值表示没有限制) 
  19.                 max-wait: -1ms 
  20.                 # 连接池中的最大空闲连接 
  21.                 max-idle: 10 
  22.                 # 连接池中的最小空闲连接 
  23.                 min-idle: 0 

引入依赖和配置后,框架会自动使用Redis存储。

总结

初步尝试还挺不错的,文档和代码示例都很全,基本功能都能满足,源码简单易懂,可以随意二开,封装度非常高,不理解原理的就很容易变成工具人了,其他的等用一段时间再评论。

参考:

http://sa-token.dev33.cn/

https://github.com/dromara/sa-token

 

责任编辑:武晓燕 来源: Java大厂面试官
相关推荐

2021-09-01 10:07:43

开发零搭建Groovy

2021-04-28 16:10:48

开发脚手架 Spring

2021-03-09 17:11:09

数据库脚手架开发

2021-05-13 17:02:38

MDC脚手架日志

2021-07-13 18:42:38

Spring Boot脚手架开发

2020-08-19 08:55:47

Redis缓存数据库

2021-04-20 19:24:16

脚手架 Java微信

2023-11-28 17:24:45

2021-06-02 17:58:49

脚手架 幂等性前端

2021-02-19 22:43:50

开发脚手架Controller

2021-07-29 18:49:49

Spring开发脚手架

2022-02-18 08:34:33

JavaSa-Token项目

2021-03-11 14:16:47

Spring Boo开发脚手架

2016-08-10 14:59:41

前端Javascript工具

2022-04-24 11:33:47

代码管理工程

2023-11-21 17:36:04

OpenFeignSentinel

2014-08-15 09:36:06

2021-01-07 05:34:07

脚手架JDK缓存

2018-06-11 14:39:57

前端脚手架工具node.js

2018-08-30 16:08:37

Node.js脚手架工具
点赞
收藏

51CTO技术栈公众号