当前位置: 首页 > 科技观察

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

时间:2023-03-19 15:32:28 科技观察

本文转载自微信公众号《Java大厂面试官》,作者laker。转载本文请联系Java大厂面试官公众号。目前我只是出于学习和尝鲜的目的进行整合。不建议在公司等正式环境中使用。公司还是推荐Shiro和SpringSecurity。(等我实战看看效果。)为什么我在尝试Sa-Token之前就拒绝了国内小作坊的开源作品?在平时的工作中认识了很多,渐渐改变了看法。其实国人的开源作品还是很香的。Api简单易用。源码和官方文档都是中文的。功能丰富,可以满足很多中文需求,各种QQ和微信交流群很活跃,总之很大程度满足了中国人的需求。在授权认证框架领域,使用最多的是Shiro和SpringSecurity。然而有一天,我在访问一个同性交友网站(github)时,突然发现Sa-Token居然有2K星,而且它的中文介绍竟然是光。看了海量的Java权限认证框架的特性和功能后,勾起了我强烈的好奇心,于是今天就尝鲜了。什么是Sa-Token?sa-token是一个轻量级的Java权限认证框架,主要解决登录认证、权限认证、Session会话、单点登录、OAuth2.0等一系列权限相关的问题,该框架旨在踢人下线、自动续费、前后端分离、分布式会话……等N多适配的通用服务,通过sa-token,可以最小化的实现系统的权限认证部分与与其他权限认证框架相比,sa-token具有以下优势:简单:零配置启动框架,真正开箱即用,低成本使用强大:集成了数十种权限相关特性,覆盖绝大部分业务场景易用性:如Silky-smoothAPI调用,大量高级特性仅需一行代码即可实现高扩展性:几乎所有组件都提供扩展接口,90%以上的l逻辑可按需改写Sa-Token能做什么?LoginVerification——轻松登录认证,并提供五种细分场景值权限验证——适配RBAC权限模型,不同角色不同授权Session会话——专业数据缓存中心踢人下线——立即清除违规用户退下线持久层扩展——可集成Redis、Memcached等专业缓存中间件,重启数据不丢失分布式会话——提供jwt集成和共享数据中心两种分布式会话解决方案单点登录——一次登录,处处模仿其他账户——操作任意用户状态数据实时临时身份切换——临时将session身份切换到另一个账号无Cookie模式——APP、小程序等前后台分离场景同终端互斥登录——如QQ手机和电脑同时在线,但是两部手机是互斥登录的多账户认证体系——例如某商城项目的user表和admin表分别进行认证花哨的token生成——内置六种token样式,也可以自定义生成token策略注解认证———鉴权与业务代码优雅分离路由拦截鉴权-根据路由拦截鉴权,可适配restful模式自动续期-提供两种token过期策略,灵活搭配,也可自动续期session管理--提供便捷灵活的会话查询接口Remembermemode——适配[Rememberme]模式,无需认证重启浏览器Spring等框架集成快速集成依赖导入cn.dev33sa-token-spring-boot-starter1.15.2》去maven中央库查看最新版本,目前是1.15.2配置文件可以零配置启动项目,同时也可以在application.yml中添加如下配置,使用框架进行定制:spring:#sa-token配置sa-token:#token名称(也cookie名称)token-name:satoken#token有效期,units默认为30天,-1表示永不过期timeout:2592000#token临时有效期(在指定时间内没有操作则认为token过期)unit:secondactivity-timeout:-1#是否允许同一个账号同时登录(true时允许一起登录,false时新登录挤出旧登录)allow-concurrent-login:true#多人同时登录时account,是否共享一个token(为true时,所有登录共享一个token,为false时每次创建一个新的token)is-share:false#tokenstyletoken-style:uuidlogin@PostMapping("/api/v1/login")@ApiOperationSupport(order=1)@ApiOperation(value="="Login")publicResponselogin(StringuserName,Stringpwd){log.info("login,username:{},pwd:{}",userName,pwd);//模拟验证用户名密码LonguserId=check(userName,pwd);StpUtil.setLoginId(userId);returnResponse.ok(StpUtil.getTokenInfo());}核心就那么一行StpUtil.setLoginId(userId),看看它为我们做了什么?源码极其简单,中文注释多,跟着走直接贴结论。创建token创建SaSession在session上记录token签名创建token,loginId映射token写入cookie底层session等存储使用Map源码如下:/***数据采集*/publicMapdataMap=newConcurrentHashMap();/***过期时间集合(单位:毫秒),记录所有key的过期时间[不是剩余存活时间]*/publicMapexpireMap=newConcurrentHashMap();调用结果如下:ResponseHeardsConnection:keep-aliveContent-Type:application/jsonDate:Fri,09Apr202107:33:59GMTKeep-Alive:timeout=60//KeySet-Cookie:LakerToken=da14afd3f4b648a889a1e51ac3ec53d7;Max-Age=1800;Expires=Fri,09-Apr-202108:03:59GMT;Path=/Transfer-Encoding:chunkedResponseBody{"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-202108:03:59GMT;Path=/Logout@PostMapping("/api/v1/loginOut")@ApiOperationSupport(order=3)@ApiOperation(value="Logout")@SaCheckLoginpublicResponseloginOut(){StpUtil.logout();returnResponse.ok();}核心也是一行StpUtil.logout(),看看它对我们做了什么?requestbodytoken尝试从header中读取token尝试从cookie中读取token删除cookie删除token,loginId映射注销session请求拦截和鉴权第一步:配置全局拦截器@ConfigurationpublicclassMySaTokenConfigimplementsWebMvcConfigurer{/***registersa-token拦截器,开启注解认证功能(如果不需要这个功能,y你可以删除这个类)*/@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newSaAnnotationInterceptor()).addPathPatterns("/**");}}第二步:在需要的类或方法上注解@SaCheckLogin被拦截:标记在方法或类上,当前会话必须处于登录状态才能通过验证@SaCheckRole("admin"):标记在方法或类上,当前session必须有指定角色ID才能通过验证@SaCheckPermission("user:add"):标记在方法或类上,当前session必须有指定权限通过验证例如:@GetMapping("/api/v1/tokenInfo")@ApiOperationSupport(order=2)@ApiOperation(value="获取当前会话的token信息")@SaCheckLoginpublicResponsetokenInfo(){returnResponse.ok(StpUtil.getTokenInfo());}加上@SaCheckLogin,接口必须处于登录状态才能通过验证。核心拦截验证在这里是如何工作的?可以看下SaAnnotationInterceptor.java的源码,基于SpringMvc拦截器测试的拦截验证。实现函数如下:验证登录验证角色验证权限实现过程原理如下:获取HttpRequest中的token尝试从请求中读取token尝试从请求体中读取token尝试读取请求体中的tokentoken从header中尝试从cookie中读取token判断token无效,token过期,被推送下线,被踢下线自动更新权限和角色扩展,直接实现了StpInterface接口,只是重写了getPermissionList和getRoleList方法。@ComponentpublicclassStpInterfaceImplimplementsStpInterface{/***返回一个账号拥有的权限码集合*/@OverridepublicListgetPermissionList(ObjectloginId,StringloginKey){xxx}/***返回一个账号拥有的角色标识集合*/@OverridepublicListgetRoleList(ObjectloginId,StringloginKey){xxx}}集群环境sa-token默认将session数据保存在内存中。这种模式读写速度最快,避免了序列化和反序列化带来的性能消耗。但是这种模式也有一些缺点,比如:重启后数据会丢失,集群模式下无法共享数据。为此,sa-token将所有的数据持久化操作抽象到SaTokenDao接口中。这样的设计可以保证开发者可以灵活的扩展框架。例如,我们可以将session数据存储在Redis、Memcached等专业的缓存中间件中,实现重启后数据不丢失,保证分布式环境下多个节点的session一致性。除了框架默认提供的基于内存的SaTokenDao实现外,我们使用官网提供的Redis扩展。依赖导入cn.dev33sa-token-dao-redis-jackson1.15.2org.apache.commonscommons-pool2》使用Jackson序列化,序列化后Session可读性高,配置文件可以手动修改spring:#redis配置redis:#Redis数据库索引(默认为0)database:0#Redis服务器地址host:127.0.0.0.1#Redis服务器连接端口port:6379#Redis服务器连接密码(默认为空)#password:#连接超时时间(毫秒)timeout:1000mslettuce:pool:#连接池最大连接数max-active:200#最大阻塞连接池的等待时间(使用负值表示不限制)max-wait:-1ms#连接池中最大空闲连接max-idle:10#连接池中最小空闲连接min-idle:0依赖和配置介绍最后,框架会自动使用Redis存储。最初的尝试是相当不错的。文档和代码示例齐全,基本功能都能满足,源码简单易懂,可以随意打开,包装度很高,不懂原理很容易成为工具人,过一段时间别人就会评论。参考:http://sa-token.dev33.cn/https://github.com/dromara/sa-token