1
0
mirror of synced 2026-05-22 22:53:16 +00:00

Compare commits

...

197 Commits

Author SHA1 Message Date
click33 308f81be31 release: v1.42.0 2025-04-11 08:38:36 +08:00
click33 f60702186d docs: 文档补全补录结构说明 2025-04-11 03:52:54 +08:00
click33 7a1095c72f feat: temp token 模块新增命名空间属性 2025-04-11 03:37:46 +08:00
click33 fab7f200cc feat: API Key 模块新增 namespace 命名空间,为多账号模式提供支持 2025-04-10 20:38:47 +08:00
刘潇 49ec4d9690 !328 feat: 所有 SaTokenDao 最终实现类,再增加 SaTokenDao 实现申明(就像 jdk 里,HashMap 会增加 Map 实现申明)。
Merge pull request !328 from 西东/dev
2025-04-10 09:09:36 +00:00
noear d82f754d09 feat: 所有 SaTokenDao 最终实现类,再增加 SaTokenDao 实现申明(就像 jdk 里,HashMap 会增加 Map 实现申明)。 2025-04-10 16:49:57 +08:00
click33 79a962460a feat: 新增 CORS 跨域策略处理函数,提供不同架构下统一的跨域处理方案 2025-04-10 16:46:45 +08:00
click33 32669cc91e docs: 文档新增 “异步 & Mock 上下文” 章节 2025-04-10 11:28:54 +08:00
click33 34f8a60b23 docs: 升级“自定义 SaTokenContext 指南”章节文档 2025-04-10 10:44:39 +08:00
click33 ec06b8f644 refactor: 临时 token 认证模块细节优化与 demo、文档补充 2025-04-09 14:43:57 +08:00
click33 fd06bfc801 docs: 升级“临时 Token 认证”章节文档 2025-04-09 14:10:40 +08:00
click33 38fd28c2ab docs: 升级“临时 Token 认证”章节文档 2025-04-09 14:05:40 +08:00
click33 662398af6a docs: 完善文档 cookieAutoFillPrefix 配置项描述 2025-04-09 12:47:54 +08:00
click33 713ac4d6e6 feat: temp token 模块增加前缀长度限制 2025-04-09 08:32:19 +08:00
click33 9ecaf72e9f feat: 重构 temp token 模块,新增 value 反查 token 机制 2025-04-09 08:15:46 +08:00
click33 14e645a8ac feat: 防火墙 hook 注册,新增 registerHookToFirst、registerHookToSecond 方法,以便更灵活的控制 hook 顺序 2025-04-08 02:41:40 +08:00
click33 8c5ea5112d docs: 补全赞助者列表 2025-04-08 02:27:26 +08:00
click33 d08885c209 docs: 新增 apikey 提交方式描述 2025-04-08 02:23:02 +08:00
click33 57fff10aef demo: 新增异步场景使用 demo。 2025-04-07 11:59:56 +08:00
click33 1925eb4081 feat: 新增 Mock 上下文 2025-04-07 11:58:08 +08:00
click33 6642f96f7e test: 修复部分错误单测代码 2025-04-07 11:57:36 +08:00
click33 62d70f0027 refactor: 优化部分类名,更语义化 2025-04-07 11:57:19 +08:00
click33 3acc7bd7af refactor: 重构所有 rpc 组件的 SaTokenContext 上下文读写策略 & 删除二级上下文模块 2025-04-07 05:41:30 +08:00
click33 c6a081ebf6 repair: 3 2025-04-07 03:26:22 +08:00
click33 de1239e9d2 repair: 2 2025-04-07 03:25:58 +08:00
click33 78de70bda6 repair: 1 2025-04-07 03:25:32 +08:00
click33 55f0c94aec refactor: 重构所有 starter 组件的 SaTokenContext 上下文读写策略 2025-04-06 23:22:01 +08:00
click33 36cc99a70c docs: 新增排错方案 QA:在 SaServletFilter 中调用 SpringMVCUtil.getRequest() 报错:非Web上下文无法获取Request? 2025-04-05 16:43:33 +08:00
click33 0a7a82fdab docs: 新增QA:如何防止 CSRF 攻击? 2025-04-05 00:52:33 +08:00
click33 601d8b1373 feat: 新增 API Key 模块 2025-04-04 23:36:59 +08:00
click33 8cff63b0fc docs: 补全 TOTP 验证器相关文档 2025-04-02 23:12:55 +08:00
click33 f75ab31222 feat: 新增 TOTP 实现 2025-04-02 23:08:47 +08:00
click33 a4c18c5238 feat: 新增 base32 编码工具类 2025-04-02 23:03:34 +08:00
click33 5f1e2bea7e docs: 新增QA:修改 hosts 文件无效可能原因排查 2025-04-02 16:59:18 +08:00
click33 660ac438c2 refactor: renewTimeout 续期方法增加 token 终端信息有效性校验 2025-04-02 08:50:25 +08:00
click33 c289ec572d feat: SaLoginParameter 支持配置 SaCookieConfig 参数 2025-04-02 07:55:15 +08:00
click33 bb5ceb1dc0 refactor: 优化 Token-Session 获取算法,减少缓存读取次数 2025-04-02 06:46:57 +08:00
click33 f2e9f7c222 feat: 新增配置项 rightNowCreateTokenSession:在登录时,是否立即创建对应的 Token-Session (true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建) 2025-04-02 05:51:18 +08:00
click33 a7f178da53 feat: sa-token-quick-login 插件支持 Http Basic 方式通过认证 2025-04-02 02:54:50 +08:00
click33 18ab60d4d2 refactor: 修复 sso 插件不正确的注释描述 2025-04-02 02:54:05 +08:00
click33 cd964d4d9f docs: 修复已过期的描述为最新版写法 2025-04-02 01:09:07 +08:00
click33 b844312f89 docs: 新增QA:如何自定义框架读取 token 的方式? 2025-04-02 00:44:35 +08:00
click33 24b49750f3 docs: test commit 2025-04-02 00:21:08 +08:00
click33 92f48256e9 feat: 新增配置项 cookieAutoFillPrefix:cookie 模式是否自动填充 token 前缀 2025-04-02 00:17:56 +08:00
click33 68939084a2 docs: 多账号模式新增注意点:运行时不可更改 LoginType 2025-04-01 18:57:16 +08:00
click33 e0fdfe84ee docs: 新增QA:Sa-Token 集成 Redis 如何集群? 2025-04-01 17:24:58 +08:00
click33 1685eebac3 docs: 新增QA:前后端一体项目下,在拦截未登录进入登录页面时,如何登录完成后原路返回? 2025-04-01 16:06:54 +08:00
click33 5d7a51214c docs: 登录认证文档添加 Cookie 查看步骤演示图 2025-04-01 15:55:10 +08:00
click33 477a5531fb docs: 添加文档开源说明 2025-04-01 15:40:01 +08:00
click33 e7d4559325 docs: 多账号模式QA:在一个接口里获取是哪个体系的账号正在登录 2025-04-01 06:47:28 +08:00
click33 8a9e76f8ca docs: 采用更细致的描述优化SSO模式三单点注销步骤 2025-04-01 06:06:15 +08:00
click33 2fbafcdd3e docs: 新增QA:解决低版本 SpringBoot (<2.2.0) 引入 Sa-Token 报错的问题 2025-04-01 05:33:10 +08:00
click33 a4b84c4df5 docs: 补全公司案例展示 2025-03-28 16:55:36 +08:00
click33 0e7e2888b6 docs: 修复 thymeleaf 章节不正确描述 2025-03-28 16:55:23 +08:00
click33 48368254e4 docs: 修复 unionid 章节错误描述 2025-03-28 16:55:03 +08:00
click33 07ae1d6921 docs: 更新日志添加导读文章视频链接 2025-03-25 16:32:33 +08:00
click33 f1155a2206 docs: 修复 Thymeleaf 集成文档不正确的依赖示例说明 2025-03-24 15:40:54 +08:00
click33 04c15e81ba docs: 补全赞助者名单 2025-03-23 15:32:06 +08:00
click33 5e8a429d37 v1.41.0 update 2025-03-21 14:22:00 +08:00
click33 db611b8337 test: 新增 Serializer 序列化模块单元测试 2025-03-21 02:05:06 +08:00
click33 8525cea74c test: 简化部分测试用例断言写法 2025-03-21 01:02:34 +08:00
click33 35cbd38b3b test: 新增 json 序列化插件的单元测试 2025-03-21 00:47:55 +08:00
click33 9058e7edc4 docs: 优化文档 2025-03-20 23:51:50 +08:00
click33 02c36fe9f8 feat: SaTokenPlugin 新增自动注入能力 2025-03-20 12:21:50 +08:00
click33 6e4bdea8c1 docs: 优化 Sa-Token 插件开发指南 章节 2025-03-20 12:13:01 +08:00
click33 4726544b85 docs: api-sign 模块新增 @SaCheckSign 注解示例与多应用模式示例 2025-03-20 09:39:43 +08:00
click33 b12b91e1ad docs: 全局配置章节 sign 模块增加 digestAlgo 配置项描述 2025-03-20 09:38:43 +08:00
click33 f881c1d069 docs: 防火墙校验 hook 注册增加 @Component 方式示例 2025-03-19 19:50:57 +08:00
click33 86158b2950 docs: 新增 自定义序列化插件 章节 2025-03-19 17:15:57 +08:00
click33 6f4772d3ab refactor: sa-token-serializer-features 默认不注册任何实现类 2025-03-19 17:12:50 +08:00
click33 05a03b61b7 docs: 完善文档: 项目目录结构介绍 2025-03-19 12:58:38 +08:00
click33 10b53c3ef0 docs: 新增顶人下线 API 介绍 2025-03-19 12:53:11 +08:00
click33 bf42588519 refactor: 默认设备类型改为 DEF (源码注释与文档部分) 2025-03-19 12:52:02 +08:00
click33 6a09d71911 feat: 新增 StpUtil.forEachTerminalList 方法,用于手动遍历一个账号的已登录终端信息列表,执行特定函数 2025-03-19 12:33:41 +08:00
click33 631db8215f docs: 完善会话查询章节文档,增加单账号会话查询的操作示例 2025-03-19 12:03:30 +08:00
click33 c6e1be58c5 fix: 修复 StpUtil.searchSessionId 等会话查询 API 在填写 null 参数时无法正确拼接字符串的问题 2025-03-19 11:57:40 +08:00
click33 4d2eb4e94b feat: SaTokenConfig 新增 replacedRange、overflowLogoutMode、logoutRange、isLogoutKeepFreezeOps、isLogoutKeepTokenSession 配置项 2025-03-19 11:03:32 +08:00
click33 12116c106e docs: 完善 登录参数 & 注销参数 章节文档 2025-03-19 08:10:18 +08:00
click33 937844f4f6 refactor: 登录参数与注销参数代码细节优化 2025-03-19 08:09:49 +08:00
click33 75df0597a0 docs: 完善 Redis 集成文档 2025-03-19 06:41:39 +08:00
click33 258e379570 feat: 新增 preview-doc.bat 文件,一键启动文档预览 2025-03-19 04:58:40 +08:00
click33 fb95acc8ce feat: 为 SaTokenPluginForJackson 指定较弱的优先级 2025-03-19 04:34:47 +08:00
click33 b25fc289da refactor: 为 sa-token-demo-quick-login-sb3/pom.xml 和 sa-token-demo-quick-login/pom.xml 增加 <relativePath/> 配置 2025-03-19 04:32:59 +08:00
click33 eaef278426 fix: 修复单元测试中不通过的代码 2025-03-19 04:10:31 +08:00
click33 5b5c031bdd feat: sa-token-quick-login 支持 SpringBoot3 项目。 Closes #IAFQNE。fix: #673 2025-03-14 19:48:45 +08:00
click33 a01eef8000 fix: 补全 SaFirewallCheckHook 全局自动注入 2025-03-14 19:44:30 +08:00
click33 bfb34293d6 refactor: BCrypt 标注为 @Deprecated 2025-03-14 00:24:32 +08:00
click33 79016e5ffe feat: 新增 SaLogoutParameter,用于控制注销会话时的各种细节 & 优化注销会话相关 API & SaLoginParameter 新增 replacedMode、overflowLogoutMode & SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE 默认值改为 DEF 2025-03-13 23:58:18 +08:00
click33 c7f27e393e refactor: 顶人下线将删除对应的 token-session 对象 2025-03-11 14:22:43 +08:00
click33 dded9237a6 refactor: 踢人下线将删除对应的 token-session 对象. 2025-03-11 13:57:38 +08:00
click33 d806f16acd refactor: 踢人下线将删除对应的 token-session 对象 2025-03-11 13:50:45 +08:00
click33 fd623c122e docs: 优化“技术求助”按钮的提示文字 2025-03-11 04:48:09 +08:00
click33 307c6a0619 refactor: 全局配置 is-share 默认值改为 false 2025-03-11 02:09:05 +08:00
click33 33c3b90abb docs: 优化 readme 2025-03-10 21:51:42 +08:00
click33 3d2cdbbb57 feat: api 参数签名模块允许多实例配置。 Closes #IAK2BI, #I9SPI1, #IAC0P9 2025-03-10 02:07:23 +08:00
click33 aef5e04abe feat: 新增 @SaCheckSign 注解鉴权,用于 API 签名参数校验 2025-03-10 00:08:59 +08:00
click33 1c4af4cc03 feat: API 参数签名配置支持自定义摘要算法 2025-03-09 19:44:45 +08:00
click33 abc2b0be69 refactor: 更换 SaMapPackage 包名 2025-03-08 20:27:07 +08:00
click33 9095ea3851 feat: 新增 sa-token-caffeine 插件 2025-03-08 20:25:37 +08:00
click33 469d387f8a chore: 优化类注释 2025-03-08 15:57:16 +08:00
click33 b2e188fc3f docs: 补全全局配置项 2025-03-08 15:30:21 +08:00
click33 4ecd71bc7e docs: 补全API手册 2025-03-08 15:28:00 +08:00
click33 c3be6304db refactor: 补正一些缺失方法 2025-03-08 15:22:33 +08:00
click33 850af6c131 docs: 重构 TokenSign -> SaTerminalInfo 2025-03-08 15:21:40 +08:00
click33 7d2fac7d98 refactor: 重构:isExtraData -> haveExtraData,以避免触发部分框架的自动序列化 2025-03-08 12:03:45 +08:00
click33 2b8489dff7 docs: 文档新增 “登录参数” 章节 2025-03-08 11:52:17 +08:00
click33 5352e973e3 docs: 完善防火墙组件相关文档 2025-03-08 00:27:02 +08:00
click33 4b02b5bb28 refactor: 将防火墙路由拦截的 path 配置改为 List<String> 数据类型 2025-03-08 00:26:29 +08:00
click33 d853d61bf6 refactor: SaFirewallCheckHookForPathDangerCharacter#dangerCharacter 数据类型更换为 List<String> 以更方便扩展 2025-03-07 23:18:40 +08:00
click33 f787d5a375 docs: 整理项目目录结构相关文档 2025-03-07 20:38:47 +08:00
click33 bc401492b5 docs: 完善 “SSO 用户数据同步 / 迁移” 章节文档 2025-03-07 11:05:09 +08:00
click33 a1189c8aaf docs: 优化 OAuth2 简述章节描述文档 2025-03-06 20:54:04 +08:00
click33 bdf5a0a264 feat: 新增 StpUtil.getTerminalListByLoginId(Object loginId) 方法,以更方便的实现单账号会话管理 2025-03-05 22:21:24 +08:00
click33 81727ec4cf feat: 新增设备锁登录示例 2025-03-05 18:36:22 +08:00
click33 6c3dd1f222 feat: 新增 isTrustDeviceId 方法,用于判断指定设备是否为可信任设备 2025-03-05 18:33:55 +08:00
click33 4e78cc8fee feat: SaTerminalInfo 新增 extraData 自定义扩展数据设置 2025-03-04 11:35:45 +08:00
click33 b45ccba778 fix: sa-token-redisx 中错误的 test 代码 2025-03-04 09:46:10 +08:00
刘潇 e47fdd82d8 !326 调整 sa-token-solon-plugin 及 dao 相关的代码
Merge pull request !326 from 西东/dev
2025-03-04 00:50:04 +00:00
noear 2be9b0f173 docs: sa-token-plugin 的 pom.xml,sa-token-redisx 调整到“通用插件”(它是通用的) 2025-03-03 20:47:11 +08:00
noear 8c256d893b feat: 添加新所菜插件 sa-token-snack3 (通用 json 序列化) 2025-03-03 20:45:42 +08:00
noear 905f6714e2 perf: sa-token-redisx 调整 SaTokenDaoOfRedisJson 类保持与 SaTokenDaoForRedisTemplate 相似的处理逻辑 2025-03-03 20:32:23 +08:00
noear caeb4eba15 perf: sa-token-solon-plugin 移除 dao 下的代码(由具体插件处理) 2025-03-03 20:23:09 +08:00
noear fce89c2efa perf: noear-snack3 升为 3.2.127 2025-03-03 20:07:42 +08:00
noear a780fa0ad7 perf: noear-redisx 升为 1.6.9 2025-03-03 20:07:12 +08:00
click33 25fcd80eb6 refactor: 重构 TokenSign -> SaTerminalInfo 2025-03-03 16:09:35 +08:00
click33 42fbb0dde8 fix: 修复 jwt mixin 模式不能正确创建 token 的问题 2025-03-02 05:10:52 +08:00
click33 0e4d812b8b refactor: 补全 SaLoginModel.java 以兼容旧版本 2025-03-02 04:57:55 +08:00
click33 59659d1c12 feat: SaLoginParameter 新增 maxTryTimes 配置 2025-03-02 04:50:53 +08:00
click33 0b85c7d094 feat: SaLoginParameter 支持 maxLoginCount 配置 2025-03-02 01:52:25 +08:00
click33 3bc9e88645 feat: SaLoginParameter 支持配置 isShare 2025-03-02 01:25:14 +08:00
click33 16cf2db334 feat: SaLoginParameter 支持配置 isConcurrent 2025-03-02 01:04:33 +08:00
click33 0743b67cf8 refactor: SaLoginModel -> SaLoginParameter 2025-03-01 21:15:37 +08:00
click33 ce74d5f41f feat: isLastingCookie 配置项支持在全局配置中定义了 2025-03-01 10:07:32 +08:00
click33 2d6c371638 fix: 解决 sa-token-jackson 插件对简单 json 字符串无法反序列化为 Map 的问题 2025-03-01 07:18:18 +08:00
click33 c830f1fbe3 docs(oauth2): 补全 OAuth2 遗漏的相关配置项 2025-03-01 05:57:06 +08:00
click33 2c707146ee refactor: 删除 sa-token-solon-plugin 插件中冗余的 OAuth2 注解鉴权处理器注册 2025-03-01 04:58:45 +08:00
click33 062572e2ee refactor: 重构 sa-token-oauth2 插件,将注解鉴权处理器的注册过程放进了SPI插件里 2025-03-01 04:51:26 +08:00
click33 b83927abdf feat: 新增 SaFirewallStrategy#removeHook 函数,用于移除指定类型的防火墙校验 hook 2025-03-01 04:27:53 +08:00
click33 d1a79ce55e feat: 增强防火墙 hook 校验能力 2025-02-28 06:13:03 +08:00
click33 8f51d1af8d feat: 新增 SaFirewallCheckHookForPathBannedCharacter 禁用字符校验 2025-02-28 05:26:50 +08:00
click33 6c55de0ef3 refactor: 重构 SaFirewallCheckHookForDirectoryTraversal 算法,使之判断的更加精准 2025-02-28 05:24:45 +08:00
click33 4ba21ffba8 feat: 新增 SaFirewallCheckHookForHeader、SaFirewallCheckHookForParameter 防火墙校验 hook 2025-02-28 02:29:20 +08:00
click33 c42e5fb34e feat: 新增 SaFirewallCheckHookForHttpMethod HttpMethod 校验 hook 2025-02-27 20:18:07 +08:00
click33 e469b76681 feat: 新增 SaFirewallCheckHookForHost host 检测 hook 2025-02-27 09:50:12 +08:00
click33 80789607fd refactor: 重构防火墙路径遍历符校验,抽离出单独的 hook 2025-02-27 07:39:29 +08:00
click33 cce77fbd49 feat: 为 sa-token-solon-plugin 添加防火墙校验过滤器 2025-02-27 06:34:01 +08:00
click33 59823fd3cd refactor: 重构相关 starter 包的防火墙过滤器代价 2025-02-27 06:15:54 +08:00
click33 db5e70db6a refactor: 重构防火墙模块,增加 hooks 机制 2025-02-27 05:55:55 +08:00
click33 a5d8e071a7 refactor: 重构 sa-token-fastjson2 插件 2025-02-26 06:21:42 +08:00
click33 03ad51ef7b refactor: 重构 sa-token-fastjson 插件 2025-02-26 06:09:55 +08:00
刘潇 ca787ec240 !324 sa-token-solon-plugin 优化 sa-token, sa-sso, sa-oauth2 适配,保持与 spirngboot 类似的适配体验
Merge pull request !324 from 西东/dev
2025-02-25 09:11:37 +00:00
noear 273e4c74db sa-token-solon-plugin 优化 sa-oauth2 适配,保持与 spirngboot 类似的适配体验 2025-02-25 17:06:41 +08:00
noear 028a79abd8 sa-token-solon-plugin 优化 sa-sso 适配,保持与 spirngboot 类似的适配体验 2025-02-25 17:01:04 +08:00
noear 561c40c3ba sa-token-solon-plugin 优化 sa-token 适配,保持与 spirngboot 类似的适配体验 2025-02-25 16:54:23 +08:00
刘潇 bf4d40cb30 !323 sa-token-solon-plugin 添加 SaBeanInject 的翻译类
Merge pull request !323 from 西东/dev
2025-02-25 06:59:07 +00:00
noear cdad8e7245 sa-token-solon-plugin 添加 SaBeanInject 的翻译类 2025-02-25 11:56:03 +08:00
click33 a94fe35e65 feat: 新增 sa-token-serializer-features 插件,用于实现各种形式的自定义字符集序列化方案 2025-02-25 03:59:51 +08:00
click33 d1be8365c4 chore: 删除不必要的 .gitignore 文件 2025-02-24 20:27:22 +08:00
click33 672015adf0 feat: 为插件新增 Install 与 Destroy 钩子函数 2025-02-24 20:20:45 +08:00
click33 c04241d3b3 refactor: SPI 插件目录改为 satoken 2025-02-24 19:48:33 +08:00
click33 a97e3f9902 feat: 插件钩子函数的注册支持连缀风格调用 2025-02-24 17:08:16 +08:00
click33 d65881b59a feat: 为插件的安装与卸载提供钩子函数支持 2025-02-24 16:41:35 +08:00
click33 048dadaff7 feat: 为序列化器新增 objectToBytesbytesToObject 方法 2025-02-24 02:11:35 +08:00
click33 d551066238 feat: 新增 base64、hex、ISO_8859_1 三种编码的序列化方案 2025-02-24 01:59:51 +08:00
click33 e898ad70c5 feat: 新增 SaSerializerTemplate 序列化器 2025-02-23 21:43:27 +08:00
click33 b90754839c refactor: 新增 sa-token-demo/pom.xml 以便在 idea 中一键导入所有 demo 项目 2025-02-23 18:28:51 +08:00
click33 5af713fc9d refactor: sa-token-spring-boot-autoconfig 默认引入 sa-token-jackson 2025-02-23 16:07:09 +08:00
click33 6c2de3c99e refactor: 重构 sa-token-redisson 插件 2025-02-23 04:06:23 +08:00
click33 f3cec926c6 refactor: 重构 sa-token-redisson 插件 2025-02-23 02:55:14 +08:00
click33 6b273a3181 refactor: 重构 sa-token-redisx 插件 2025-02-22 18:38:05 +08:00
click33 83be085ffc refactor: 重构部分插件 2025-02-22 18:14:19 +08:00
click33 ca64e44fc0 refactor: 重构 sa-token-temp-jwt 插件 2025-02-22 18:07:30 +08:00
click33 d5deb72c9a refactor: 重构插件 sa-token-dubbo & sa-token-dubbo3 2025-02-22 18:00:11 +08:00
click33 349213032b refactor: 重构 sa-token-thymeleaf 插件 2025-02-22 17:06:58 +08:00
click33 e5c21b478d Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2025-02-22 16:58:57 +08:00
click33 5dbaab1966 refactor: 重构 sa-token-alone-redis 插件 2025-02-22 16:58:34 +08:00
click33 935a8db3ae refactor: 重构 sa-token-redis-template-jdk-serializer 插件 2025-02-22 16:14:45 +08:00
click33 362b08b9b8 refactor: 重构 SaTokenDao 相关代码,减少重复代码 2025-02-22 04:36:02 +08:00
click33 7c3febda60 refactor: 重构 sa-token-fastjson、sa-token-fastjson2 适配 SPI 机制 2025-02-22 02:56:49 +08:00
click33 096e2bbb63 Merge pull request #743 from fangzhengjin/dev
feat: 支持自定义 autoRenew 逻辑
2025-02-21 21:57:26 +08:00
ZhengJin 87d2a1156b feat: 支持自定义 autoRenew 逻辑 2025-02-21 21:54:16 +08:00
click33 0d9fed1558 refactor: 重构部分 sa-token-redis 插件,适配 SPI 机制 2025-02-21 21:27:34 +08:00
click33 a4c9dc1e68 feat: 新增 SaTokenPlugin 接口,重构插件体系 2025-02-21 02:32:57 +08:00
click33 54bf228ec9 refactor: 重构 SaTokenDao 接口,拆分存储与序列化操作 2025-02-20 19:33:22 +08:00
click33 e6dd850752 refactor: 重构 JSON 转换器模块 2025-02-20 18:38:22 +08:00
click33 ede9aba865 fix: 将 SaDisableWrapperInfo.createNotDisabled() 默认返回值封禁等级改为 -2,以保证向之前版本兼容 2025-02-20 03:10:03 +08:00
click33 ac723f2ddb chore: 优化注释 2025-02-19 22:21:53 +08:00
刘潇 88e42c868d !321 sa-token-solon-plugin 优化 SaTokenFilter 获取主处理的代码
Merge pull request !321 from 西东/dev
2025-02-19 14:20:01 +00:00
click33 bc5262373a docs: 新增QA“多个项目共用同一个 redis,怎么防止冲突?” 2025-02-19 22:16:02 +08:00
click33 855e13f5f1 docs: 修复错别字 2025-02-19 21:18:35 +08:00
click33 3e95c527b7 fix(code): 修复 StpUtil.setTokenValue("xxx"); 方法 loginModel.getIsWriteHeader() 空指针的问题。 closes: #IBKSM0 2025-02-19 18:03:12 +08:00
click33 3892f18a16 fix(core): 修复 StpUtil.setTokenValue("xxx"); 方法 loginModel.getIsWriteHeader() 空指针的问题。 fixes #IBKSM0 2025-02-19 18:01:23 +08:00
click33 14c57c6599 fix(code): 修复 StpUtil.setTokenValue("xxx"); 方法 loginModel.getIsWriteHeader() 空指针的问题 fixes: IBKSM0 2025-02-19 17:57:10 +08:00
click33 29c1bd9436 docs: 更新日志增加版本更新导读视频与文章链接. 2025-02-19 15:32:49 +08:00
click33 ba3ad31d47 docs: 更新日志增加版本更新导读视频与文章链接 2025-02-15 21:55:13 +08:00
click33 b9a012a056 v1.40.0 update ... 2025-02-03 07:30:07 +08:00
noear de27d08824 sa-token-solon-plugin 优化 SaTokenFilter 获取主处理的代码 2024-12-07 13:21:12 +08:00
628 changed files with 22482 additions and 9250 deletions
+14 -2
View File
@@ -1,12 +1,12 @@
<p align="center">
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.40.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.42.0</h1>
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg?theme=gvp"></a>
<a href="https://gitcode.com/dromara/sa-token/overview"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://gitcode.com/dromara/sa-token/stargazers"><img src="https://gitcode.com/dromara/Sa-Token/star/badge.svg"></a>
<a href="https://github.com/dromara/sa-token/stargazers"><img src="https://img.shields.io/github/stars/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/network/members"><img src="https://img.shields.io/github/forks/dromara/sa-token?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/watchers"><img src="https://img.shields.io/github/watchers/dromara/sa-token?style=flat-square&logo=GitHub"></a>
@@ -23,6 +23,18 @@
Sa-Token 是一个轻量级 Java 权限认证框架,目前拥有五大核心模块:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权。
要在 SpringBoot 项目中使用 Sa-Token,你只需要在 pom.xml 中引入依赖:
``` xml
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.42.0</version>
</dependency>
```
除了 SpringBoot2、Sa-Token 还为 SpringBoot3、Solon、JFinal 等常见 Web 框架提供集成包,做到真正的开箱即用。
<details>
+6
View File
@@ -8,17 +8,23 @@ cd sa-token-demo
cd sa-token-demo-alone-redis & call mvn clean & cd ..
cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
cd sa-token-demo-apikey & call mvn clean & cd ..
cd sa-token-demo-async & call mvn clean & cd ..
cd sa-token-demo-beetl & call mvn clean & cd ..
cd sa-token-demo-bom-import & call mvn clean & cd ..
cd sa-token-demo-case & call mvn clean & cd ..
cd sa-token-demo-device-lock & call mvn clean & cd ..
cd sa-token-demo-grpc & call mvn clean & cd ..
cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
cd sa-token-demo-caffeine & call mvn clean & cd ..
cd sa-token-demo-jwt & call mvn clean & cd ..
cd sa-token-demo-quick-login & call mvn clean & cd ..
cd sa-token-demo-quick-login-sb3 & call mvn clean & cd ..
cd sa-token-demo-solon & call mvn clean & cd ..
cd sa-token-demo-solon-redisson & call mvn clean & cd ..
cd sa-token-demo-springboot & call mvn clean & cd ..
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-low-version & call mvn clean & cd ..
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
cd sa-token-demo-ssm & call mvn clean & cd ..
+1 -1
View File
@@ -37,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<revision>1.40.0</revision>
<revision>1.42.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
+4
View File
@@ -0,0 +1,4 @@
:: 运行前需要安装 browser-sync:
:: npm install -g browser-sync
cd sa-token-doc & browser-sync start --server --files ""
-13
View File
@@ -1,13 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
.iml
+27 -7
View File
@@ -13,7 +13,7 @@
<url>https://github.com/dromara/sa-token</url>
<properties>
<revision>1.40.0</revision>
<revision>1.42.0</revision>
</properties>
<dependencyManagement>
@@ -111,17 +111,27 @@
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<artifactId>sa-token-redis-template</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-fastjson</artifactId>
<artifactId>sa-token-jackson</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-fastjson2</artifactId>
<artifactId>sa-token-fastjson</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-fastjson2</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-snack3</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
@@ -131,12 +141,12 @@
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson-jackson</artifactId>
<artifactId>sa-token-redisson-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redisson-jackson2</artifactId>
<artifactId>sa-token-redisson</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
@@ -151,7 +161,7 @@
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dialect-thymeleaf</artifactId>
<artifactId>sa-token-thymeleaf</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
@@ -194,6 +204,16 @@
<artifactId>sa-token-temp-jwt</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template-jdk-serializer</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-serializer-features</artifactId>
<version>${revision}</version>
</dependency>
<!-- endregion-->
</dependencies>
-13
View File
@@ -1,13 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
.iml
@@ -15,11 +15,13 @@
*/
package cn.dev33.satoken;
import cn.dev33.satoken.apikey.SaApiKeyTemplate;
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoader;
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoaderDefaultImpl;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.error.SaErrorCode;
@@ -30,14 +32,16 @@ import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.log.SaLog;
import cn.dev33.satoken.log.SaLogForConsole;
import cn.dev33.satoken.same.SaSameTemplate;
import cn.dev33.satoken.secure.totp.SaTotpTemplate;
import cn.dev33.satoken.serializer.SaSerializerTemplate;
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJson;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.temp.SaTempDefaultImpl;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.temp.SaTempTemplate;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.LinkedHashMap;
@@ -141,7 +145,7 @@ public class SaManager {
}
/**
* 一级上下文 SaTokenContextContext
* 上下文 SaTokenContext
*/
private volatile static SaTokenContext saTokenContext;
public static void setSaTokenContext(SaTokenContext saTokenContext) {
@@ -149,62 +153,33 @@ public class SaManager {
SaTokenEventCenter.doRegisterComponent("SaTokenContext", saTokenContext);
}
public static SaTokenContext getSaTokenContext() {
return saTokenContext;
}
/**
* 二级上下文 SaTokenSecondContext
*/
private volatile static SaTokenSecondContext saTokenSecondContext;
public static void setSaTokenSecondContext(SaTokenSecondContext saTokenSecondContext) {
SaManager.saTokenSecondContext = saTokenSecondContext;
SaTokenEventCenter.doRegisterComponent("SaTokenSecondContext", saTokenSecondContext);
}
public static SaTokenSecondContext getSaTokenSecondContext() {
return saTokenSecondContext;
}
/**
* 获取一个可用的 SaTokenContext (按照一级上下文、二级上下文、默认上下文的顺序来判断)
* @return /
*/
public static SaTokenContext getSaTokenContextOrSecond() {
// s1. 一级Context可用时返回一级Context
if(saTokenContext != null) {
if(saTokenSecondContext == null || saTokenContext.isValid()) {
// 因为 isValid 是一个耗时操作,所以此处假定:二级Context为null的情况下无需验证一级Context有效性
// 这样可以提升6倍左右的上下文获取速度
return saTokenContext;
if (saTokenContext == null) {
synchronized (SaManager.class) {
if (saTokenContext == null) {
SaManager.saTokenContext = new SaTokenContextForThreadLocal();
}
}
}
// s2. 一级Context不可用时判断二级Context是否可用
if(saTokenSecondContext != null && saTokenSecondContext.isValid()) {
return saTokenSecondContext;
}
// s3. 都不行,就返回默认的 Context
return SaTokenContextDefaultImpl.defaultContext;
return saTokenContext;
}
/**
* 临时 token 认证模块
*/
private volatile static SaTempInterface saTemp;
public static void setSaTemp(SaTempInterface saTemp) {
SaManager.saTemp = saTemp;
SaTokenEventCenter.doRegisterComponent("SaTempInterface", saTemp);
private volatile static SaTempTemplate saTempTemplate;
public static void setSaTempTemplate(SaTempTemplate saTempTemplate) {
SaManager.saTempTemplate = saTempTemplate;
SaTokenEventCenter.doRegisterComponent("SaTempTemplate", saTempTemplate);
}
public static SaTempInterface getSaTemp() {
if (saTemp == null) {
public static SaTempTemplate getSaTempTemplate() {
if (saTempTemplate == null) {
synchronized (SaManager.class) {
if (saTemp == null) {
SaManager.saTemp = new SaTempDefaultImpl();
if (saTempTemplate == null) {
SaManager.saTempTemplate = new SaTempTemplate();
}
}
}
return saTemp;
return saTempTemplate;
}
/**
@@ -226,6 +201,25 @@ public class SaManager {
return saJsonTemplate;
}
/**
* 序列化器
*/
private volatile static SaSerializerTemplate saSerializerTemplate;
public static void setSaSerializerTemplate(SaSerializerTemplate saSerializerTemplate) {
SaManager.saSerializerTemplate = saSerializerTemplate;
SaTokenEventCenter.doRegisterComponent("SaSerializerTemplate", saSerializerTemplate);
}
public static SaSerializerTemplate getSaSerializerTemplate() {
if (saSerializerTemplate == null) {
synchronized (SaManager.class) {
if (saSerializerTemplate == null) {
SaManager.saSerializerTemplate = new SaSerializerTemplateForJson();
}
}
}
return saSerializerTemplate;
}
/**
* API 参数签名
*/
@@ -275,7 +269,67 @@ public class SaManager {
public static SaLog getLog() {
return SaManager.log;
}
/**
* TOTP 算法类,支持 生成/验证 动态一次性密码
*/
private volatile static SaTotpTemplate totpTemplate;
public static void setSaTotpTemplate(SaTotpTemplate totpTemplate) {
SaManager.totpTemplate = totpTemplate;
SaTokenEventCenter.doRegisterComponent("SaTotpTemplate", totpTemplate);
}
public static SaTotpTemplate getSaTotpTemplate() {
if (totpTemplate == null) {
synchronized (SaManager.class) {
if (totpTemplate == null) {
SaManager.totpTemplate = new SaTotpTemplate();
}
}
}
return totpTemplate;
}
/**
* ApiKey 数据加载器
*/
private volatile static SaApiKeyDataLoader apiKeyDataLoader;
public static void setSaApiKeyDataLoader(SaApiKeyDataLoader apiKeyDataLoader) {
SaManager.apiKeyDataLoader = apiKeyDataLoader;
SaTokenEventCenter.doRegisterComponent("SaApiKeyDataLoader", apiKeyDataLoader);
}
public static SaApiKeyDataLoader getSaApiKeyDataLoader() {
if (apiKeyDataLoader == null) {
synchronized (SaManager.class) {
if (apiKeyDataLoader == null) {
SaManager.apiKeyDataLoader = new SaApiKeyDataLoaderDefaultImpl();
}
}
}
return apiKeyDataLoader;
}
/**
* ApiKey 操作类
*/
private volatile static SaApiKeyTemplate apiKeyTemplate;
public static void setSaApiKeyTemplate(SaApiKeyTemplate apiKeyTemplate) {
SaManager.apiKeyTemplate = apiKeyTemplate;
SaTokenEventCenter.doRegisterComponent("SaApiKeyTemplate", apiKeyTemplate);
}
public static SaApiKeyTemplate getSaApiKeyTemplate() {
if (apiKeyTemplate == null) {
synchronized (SaManager.class) {
if (apiKeyTemplate == null) {
SaManager.apiKeyTemplate = new SaApiKeyTemplate();
}
}
}
return apiKeyTemplate;
}
// ------------------- StpLogic 相关 -------------------
/**
* StpLogic 集合, 记录框架所有成功初始化的 StpLogic
*/
@@ -0,0 +1,49 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* API Key 校验:指定请求中必须包含有效的 ApiKey ,并且包含指定的 scope
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
* @since 1.42.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckApiKey {
/**
* 指定 API key 必须包含的权限 [ 数组 ]
*
* @return /
*/
String [] scope() default {};
/**
* 验证模式:AND | OR,默认AND
*
* @return /
*/
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,50 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证校验:必须具有正确的参数签名才可以通过校验
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
* @since 1.41.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface SaCheckSign {
/**
* 多实例下的 appid 值,用于区分不同的实例,如不填写则代表使用全局默认实例 <br/>
* 允许以 #{} 的形式指定为请求参数,如:#{appid}
*
* @return /
*/
String appid() default "";
/**
* 指定参与签名的参数有哪些,如果不填写则默认为全部参数
*
* @return /
*/
String [] verifyParams() default {};
}
@@ -0,0 +1,53 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckApiKey;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.apikey.SaApiKeyUtil;
import cn.dev33.satoken.context.SaHolder;
import java.lang.reflect.Method;
/**
* 注解 SaCheckApiKey 的处理器
*
* @author click33
* @since 1.42.0
*/
public class SaCheckApiKeyHandler implements SaAnnotationHandlerInterface<SaCheckApiKey> {
@Override
public Class<SaCheckApiKey> getHandlerAnnotationClass() {
return SaCheckApiKey.class;
}
@Override
public void checkMethod(SaCheckApiKey at, Method method) {
_checkMethod(at.scope(), at.mode());
}
public static void _checkMethod(String[] scope, SaMode mode) {
String apiKey = SaApiKeyUtil.readApiKeyValue(SaHolder.getRequest());
if(mode == SaMode.AND) {
SaApiKeyUtil.checkApiKeyScope(apiKey, scope);
} else {
SaApiKeyUtil.checkApiKeyScopeOr(apiKey, scope);
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckSign;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sign.SaSignMany;
import java.lang.reflect.Method;
/**
* 注解 SaCheckSign 的处理器
*
* @author click33
* @since 1.41.0
*/
public class SaCheckSignHandler implements SaAnnotationHandlerInterface<SaCheckSign> {
@Override
public Class<SaCheckSign> getHandlerAnnotationClass() {
return SaCheckSign.class;
}
@Override
public void checkMethod(SaCheckSign at, Method method) {
_checkMethod(at.appid(), at.verifyParams());
}
public static void _checkMethod(String appid, String[] verifyParams) {
SaRequest req = SaHolder.getRequest();
// 如果 appid 为 #{} 格式,则从请求参数中获取
if(appid.startsWith("#{") && appid.endsWith("}")) {
String reqParamName = appid.substring(2, appid.length() - 1);
appid = req.getParam(reqParamName);
}
SaSignMany.getSignTemplate(appid).checkRequest(req, verifyParams);
}
}
@@ -0,0 +1,559 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.apikey;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.ApiKeyException;
import cn.dev33.satoken.exception.ApiKeyScopeException;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.raw.SaRawSessionDelegator;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.ArrayList;
import java.util.List;
/**
* API Key 操作类
*
* @author click33
* @since 1.42.0
*/
public class SaApiKeyTemplate {
/**
*默认命名空间
*/
public static final String DEFAULT_NAMESPACE = "apikey";
/**
* 命名空间
*/
public String namespace;
/**
* Raw Session 读写委托
*/
public SaRawSessionDelegator rawSessionDelegator;
/**
* 在 raw-session 中的保存索引列表使用的 key
*/
public static final String API_KEY_LIST = "__HD_API_KEY_LIST";
public SaApiKeyTemplate(){
this(DEFAULT_NAMESPACE);
}
/**
* 实例化
* @param namespace 命名空间,用于多实例隔离
*/
public SaApiKeyTemplate(String namespace){
if(SaFoxUtil.isEmpty(namespace)) {
throw new ApiKeyException("namespace 不能为空");
}
this.namespace = namespace;
this.rawSessionDelegator = new SaRawSessionDelegator(namespace);
}
// ------------------- ApiKey
/**
* 根据 apiKey 从 Cache 获取 ApiKeyModel 信息
* @param apiKey /
* @return /
*/
public ApiKeyModel getApiKeyModelFromCache(String apiKey) {
return getSaTokenDao().getObject(splicingApiKeySaveKey(apiKey), ApiKeyModel.class);
}
/**
* 根据 apiKey 从 Database 获取 ApiKeyModel 信息
* @param apiKey /
* @return /
*/
public ApiKeyModel getApiKeyModelFromDatabase(String apiKey) {
return SaManager.getSaApiKeyDataLoader().getApiKeyModelFromDatabase(namespace, apiKey);
}
/**
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
* @param apiKey /
* @return /
*/
public ApiKeyModel getApiKey(String apiKey) {
if(apiKey == null) {
return null;
}
// 先从缓存中获取,缓存中找不到就尝试从数据库获取
ApiKeyModel apiKeyModel = getApiKeyModelFromCache(apiKey);
if(apiKeyModel == null) {
apiKeyModel = getApiKeyModelFromDatabase(apiKey);
saveApiKey(apiKeyModel);
}
return apiKeyModel;
}
/**
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
* @param apiKey /
* @return /
*/
public ApiKeyModel checkApiKey(String apiKey) {
ApiKeyModel ak = getApiKey(apiKey);
if(ak == null) {
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
}
if(ak.timeExpired()) {
throw new ApiKeyException("API Key 已过期: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12302);
}
if(! ak.getIsValid()) {
throw new ApiKeyException("API Key 已被禁用: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12303);
}
return ak;
}
/**
* 持久化:ApiKeyModel
* @param ak /
*/
public void saveApiKey(ApiKeyModel ak) {
if(ak == null) {
return;
}
// 数据自检
ak.checkByCanSaved();
// 保存 ApiKeyModel
String saveKey = splicingApiKeySaveKey(ak.getApiKey());
if(ak.timeExpired()) {
getSaTokenDao().deleteObject(saveKey);
} else {
getSaTokenDao().setObject(saveKey, ak, ak.expiresIn());
}
// 记录索引
if (getIsRecordIndex()) {
// 添加索引
SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId());
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
if(! apiKeyList.contains(ak.getApiKey())) {
apiKeyList.add(ak.getApiKey());
session.set(API_KEY_LIST, apiKeyList);
}
// 调整 ttl
adjustIndex(ak.getLoginId(), session);
}
}
/**
* 获取 ApiKey 所代表的 LoginId
* @param apiKey ApiKey
* @return LoginId
*/
public Object getLoginIdByApiKey(String apiKey) {
return checkApiKey(apiKey).getLoginId();
}
/**
* 删除 ApiKey
* @param apiKey ApiKey
*/
public void deleteApiKey(String apiKey) {
// 删 ApiKeyModel
ApiKeyModel ak = getApiKeyModelFromCache(apiKey);
if(ak == null) {
return;
}
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
// 删索引
if(getIsRecordIndex()) {
// RawSession 中不存在,提前退出
SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId(), false);
if(session == null) {
return;
}
// 索引无记录,提前退出
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
if(! apiKeyList.contains(apiKey)) {
return;
}
// 如果只有一个 ApiKey,则整个 RawSession 删掉
if (apiKeyList.size() == 1) {
rawSessionDelegator.deleteSessionById(ak.getLoginId());
} else {
// 否则移除此 ApiKey 并保存
apiKeyList.remove(apiKey);
session.set(API_KEY_LIST, apiKeyList);
}
}
}
/**
* 删除指定 loginId 的所有 ApiKey
* @param loginId /
*/
public void deleteApiKeyByLoginId(Object loginId) {
// 先判断是否开启索引
if(! getIsRecordIndex()) {
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 deleteApiKeyByLoginId 操作");
return;
}
// RawSession 中不存在,提前退出
SaSession session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) {
return;
}
// 先删 ApiKeyModel
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
for (String apiKey : apiKeyList) {
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
}
// 再删索引
rawSessionDelegator.deleteSessionById(loginId);
}
// ------- 创建
/**
* 创建一个 ApiKeyModel 对象
*
* @return /
*/
public ApiKeyModel createApiKeyModel() {
String apiKey = SaStrategy.instance.generateUniqueToken.execute(
"API Key",
SaManager.getConfig().getMaxTryTimes(),
this::randomApiKeyValue,
_apiKey -> getApiKey(_apiKey) == null
);
return new ApiKeyModel().setApiKey(apiKey);
}
/**
* 创建一个 ApiKeyModel 对象
*
* @return /
*/
public ApiKeyModel createApiKeyModel(Object loginId) {
long timeout = SaManager.getConfig().getApiKey().getTimeout();
long expiresTime = (timeout == SaTokenDao.NEVER_EXPIRE) ? SaTokenDao.NEVER_EXPIRE : System.currentTimeMillis() + timeout * 1000;
return createApiKeyModel()
.setLoginId(loginId)
.setIsValid(true)
.setExpiresTime(expiresTime)
;
}
/**
* 随机一个 ApiKey 码
*
* @return /
*/
public String randomApiKeyValue() {
return SaManager.getConfig().getApiKey().getPrefix() + SaFoxUtil.getRandomString(36);
}
// ------------------- 校验
/**
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public boolean hasApiKeyScope(String apiKey, String... scopes) {
try {
checkApiKeyScope(apiKey, scopes);
return true;
} catch (ApiKeyException e) {
return false;
}
}
/**
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public void checkApiKeyScope(String apiKey, String... scopes) {
ApiKeyModel ak = checkApiKey(apiKey);
if(SaFoxUtil.isEmptyArray(scopes)) {
return;
}
for (String scope : scopes) {
if(! ak.getScopes().contains(scope)) {
throw new ApiKeyScopeException("该 API Key 不具备 Scope" + scope)
.setApiKey(apiKey)
.setScope(scope)
.setCode(SaErrorCode.CODE_12311);
}
}
}
/**
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
try {
checkApiKeyScopeOr(apiKey, scopes);
return true;
} catch (ApiKeyException e) {
return false;
}
}
/**
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public void checkApiKeyScopeOr(String apiKey, String... scopes) {
ApiKeyModel ak = checkApiKey(apiKey);
if(SaFoxUtil.isEmptyArray(scopes)) {
return;
}
for (String scope : scopes) {
if(ak.getScopes().contains(scope)) {
return;
}
}
throw new ApiKeyScopeException("该 API Key 不具备 Scope" + scopes[0])
.setApiKey(apiKey)
.setScope(scopes[0])
.setCode(SaErrorCode.CODE_12311);
}
/**
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
* @param apiKey /
* @param loginId /
*/
public boolean isApiKeyLoginId(String apiKey, Object loginId) {
try {
checkApiKeyLoginId(apiKey, loginId);
return true;
} catch (ApiKeyException e) {
return false;
}
}
/**
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
*
* @param apiKey /
* @param loginId /
*/
public void checkApiKeyLoginId(String apiKey, Object loginId) {
ApiKeyModel ak = getApiKey(apiKey);
if(ak == null) {
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
}
if (SaFoxUtil.notEquals(String.valueOf(ak.getLoginId()), String.valueOf(loginId))) {
throw new ApiKeyException("该 API Key 不属于用户: " + loginId)
.setApiKey(apiKey)
.setCode(SaErrorCode.CODE_12312);
}
}
// ------------------- 索引操作
/**
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
* @param loginId /
* @param session 可填写 null,代表使用 loginId 现场查询
*/
public void adjustIndex(Object loginId, SaSession session) {
// 先判断是否开启索引
if(! getIsRecordIndex()) {
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 adjustIndex 操作");
return;
}
// 未提供则现场查询
if(session == null) {
session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) {
return;
}
}
// 重新整理索引列表
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
ArrayList<String> apiKeyNewList = new ArrayList<>();
ArrayList<ApiKeyModel> apiKeyModelList = new ArrayList<>();
for (String apikey : apiKeyList) {
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
if(ak == null || ak.timeExpired()) {
continue;
}
apiKeyNewList.add(apikey);
apiKeyModelList.add(ak);
}
// 如果队列里已无有效值,则删除该 session
if(apiKeyNewList.isEmpty()) {
rawSessionDelegator.deleteSessionById(loginId);
return;
}
session.set(API_KEY_LIST, apiKeyNewList);
// 调整 SaSession TTL
long maxTtl = 0;
for (ApiKeyModel ak : apiKeyModelList) {
long ttl = ak.expiresIn();
if(ttl == SaTokenDao.NEVER_EXPIRE) {
maxTtl = SaTokenDao.NEVER_EXPIRE;
break;
}
if(ttl > maxTtl) {
maxTtl = ttl;
}
}
if(maxTtl != 0) {
session.updateTimeout(maxTtl);
}
}
/**
* 获取指定 loginId 的 ApiKey 列表记录
* @param loginId /
* @return /
*/
public List<ApiKeyModel> getApiKeyList(Object loginId) {
// 先判断是否开启索引
if(! getIsRecordIndex()) {
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 getApiKeyList 操作");
return new ArrayList<>();
}
// 先查 RawSession
List<ApiKeyModel> apiKeyModelList = new ArrayList<>();
SaSession session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) {
return apiKeyModelList;
}
// 从 RawSession 遍历查询
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
for (String apikey : apiKeyList) {
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
if(ak == null || ak.timeExpired()) {
continue;
}
apiKeyModelList.add(ak);
}
return apiKeyModelList;
}
// ------------------- 请求查询
/**
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
*/
public String readApiKeyValue(SaRequest request) {
// 优先从请求参数中获取
String apiKey = request.getParam(namespace);
if(SaFoxUtil.isNotEmpty(apiKey)) {
return apiKey;
}
// 然后请求头
apiKey = request.getHeader(namespace);
if(SaFoxUtil.isNotEmpty(apiKey)) {
return apiKey;
}
// 最后从 Authorization 中获取
apiKey = SaHttpBasicUtil.getAuthorizationValue();
if(SaFoxUtil.isNotEmpty(apiKey)) {
if(apiKey.endsWith(":")) {
apiKey = apiKey.substring(0, apiKey.length() - 1);
}
return apiKey;
}
return null;
}
/**
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
*/
public ApiKeyModel currentApiKey() {
String readApiKeyValue = readApiKeyValue(SaHolder.getRequest());
return checkApiKey(readApiKeyValue);
}
// ------------------- 拼接key
/**
* 拼接keyApiKey 持久化
* @param apiKey ApiKey
* @return key
*/
public String splicingApiKeySaveKey(String apiKey) {
return getSaTokenConfig().getTokenName() + ":" + namespace + ":" + apiKey;
}
// -------- bean 对象代理
/**
* 获取使用的 getSaTokenDao 实例
*
* @return /
*/
public SaTokenDao getSaTokenDao() {
return SaManager.getSaTokenDao();
}
/**
* 获取使用的 SaTokenConfig 实例
*
* @return /
*/
public SaTokenConfig getSaTokenConfig() {
return SaManager.getConfig();
}
/**
* 是否保存索引信息
*/
public boolean getIsRecordIndex() {
return SaManager.getSaApiKeyDataLoader().getIsRecordIndex();
}
}
@@ -0,0 +1,200 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.apikey;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.session.SaSession;
import java.util.List;
/**
* API Key 操作工具类
*
* @author click33
* @since 1.42.0
*/
public class SaApiKeyUtil {
/**
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
* @param apiKey /
* @return /
*/
public static ApiKeyModel getApiKey(String apiKey) {
return SaManager.getSaApiKeyTemplate().getApiKey(apiKey);
}
/**
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
* @param apiKey /
* @return /
*/
public static ApiKeyModel checkApiKey(String apiKey) {
return SaManager.getSaApiKeyTemplate().checkApiKey(apiKey);
}
/**
* 持久化:ApiKeyModel
* @param ak /
*/
public static void saveApiKey(ApiKeyModel ak) {
SaManager.getSaApiKeyTemplate().saveApiKey(ak);
}
/**
* 获取 ApiKey 所代表的 LoginId
* @param apiKey ApiKey
* @return LoginId
*/
public static Object getLoginIdByApiKey(String apiKey) {
return SaManager.getSaApiKeyTemplate().getLoginIdByApiKey(apiKey);
}
/**
* 删除 ApiKey
* @param apiKey ApiKey
*/
public static void deleteApiKey(String apiKey) {
SaManager.getSaApiKeyTemplate().deleteApiKey(apiKey);
}
/**
* 删除指定 loginId 的所有 ApiKey
* @param loginId /
*/
public static void deleteApiKeyByLoginId(Object loginId) {
SaManager.getSaApiKeyTemplate().deleteApiKeyByLoginId(loginId);
}
// ------- 创建
/**
* 创建一个 ApiKeyModel 对象
*
* @return /
*/
public static ApiKeyModel createApiKeyModel() {
return SaManager.getSaApiKeyTemplate().createApiKeyModel();
}
/**
* 创建一个 ApiKeyModel 对象
*
* @return /
*/
public static ApiKeyModel createApiKeyModel(Object loginId) {
return SaManager.getSaApiKeyTemplate().createApiKeyModel(loginId);
}
// ------------------- Scope
/**
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public static boolean hasApiKeyScope(String apiKey, String... scopes) {
return SaManager.getSaApiKeyTemplate().hasApiKeyScope(apiKey, scopes);
}
/**
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public static void checkApiKeyScope(String apiKey, String... scopes) {
SaManager.getSaApiKeyTemplate().checkApiKeyScope(apiKey, scopes);
}
/**
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public static boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
return SaManager.getSaApiKeyTemplate().hasApiKeyScopeOr(apiKey, scopes);
}
/**
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
* @param apiKey ApiKey
* @param scopes 需要校验的权限列表
*/
public static void checkApiKeyScopeOr(String apiKey, String... scopes) {
SaManager.getSaApiKeyTemplate().checkApiKeyScopeOr(apiKey, scopes);
}
/**
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
* @param apiKey /
* @param loginId /
*/
public static boolean isApiKeyLoginId(String apiKey, Object loginId) {
return SaManager.getSaApiKeyTemplate().isApiKeyLoginId(apiKey, loginId);
}
/**
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
*
* @param apiKey /
* @param loginId /
*/
public static void checkApiKeyLoginId(String apiKey, Object loginId) {
SaManager.getSaApiKeyTemplate().checkApiKeyLoginId(apiKey, loginId);
}
// ------------------- 请求查询
/**
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
*/
public static String readApiKeyValue(SaRequest request) {
return SaManager.getSaApiKeyTemplate().readApiKeyValue(request);
}
/**
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
*/
public static ApiKeyModel currentApiKey() {
return SaManager.getSaApiKeyTemplate().currentApiKey();
}
// ------------------- 索引操作
/**
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
* @param loginId /
* @param session 可填写 null,代表使用 loginId 现场查询
*/
public static void adjustIndex(Object loginId, SaSession session) {
SaManager.getSaApiKeyTemplate().adjustIndex(loginId, session);
}
/**
* 获取指定 loginId 的 ApiKey 列表记录
* @param loginId /
* @return /
*/
public static List<ApiKeyModel> getApiKeyList(Object loginId) {
return SaManager.getSaApiKeyTemplate().getApiKeyList(loginId);
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.apikey.loader;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
/**
* ApiKey 数据加载器
*
* @author click33
* @since 1.42.0
*/
public interface SaApiKeyDataLoader {
/**
* 获取:框架是否保存索引信息
*
* @return /
*/
default Boolean getIsRecordIndex() {
return SaManager.getConfig().getApiKey().getIsRecordIndex();
}
/**
* 根据 apiKey 从数据库获取 ApiKeyModel 信息 (实现此方法无需为数据做缓存处理,框架内部已包含缓存逻辑)
*
* @param namespace /
* @param apiKey /
* @return ApiKeyModel
*/
default ApiKeyModel getApiKeyModelFromDatabase(String namespace, String apiKey) {
return null;
}
}
@@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.temp;
package cn.dev33.satoken.apikey.loader;
/**
* Sa-Token 临时令牌验证模块 默认实现类
* ApiKey 数据加载器 默认实现类
*
* @author click33
* @since 1.20.0
* @since 1.42.0
*/
public class SaTempDefaultImpl implements SaTempInterface {
public class SaApiKeyDataLoaderDefaultImpl implements SaApiKeyDataLoader {
// be empty of
}
@@ -0,0 +1,380 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.apikey.model;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.ApiKeyException;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
import java.util.*;
/**
* Model: API Key
*
* @author click33
* @since 1.41.0
*/
public class ApiKeyModel implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 名称
*/
private String title;
/**
* 介绍
*/
private String intro;
/**
* ApiKey 值
*/
private String apiKey;
/**
* 账号 id
*/
private Object loginId;
/**
* ApiKey 创建时间,13位时间戳
*/
private long createTime;
/**
* ApiKey 到期时间,13位时间戳 (-1=永不过期)
*/
private long expiresTime;
/**
* 是否有效 (true=生效, false=禁用)
*/
private Boolean isValid = true;
/**
* 授权范围
*/
private List<String> scopes = new ArrayList<>();
/**
* 扩展数据
*/
private Map<String, Object> extraData;
/**
* 构造函数
*/
public ApiKeyModel() {
this.createTime = System.currentTimeMillis();
}
// method
/**
* 添加 Scope
* @param scope /
* @return /
*/
public ApiKeyModel addScope(String ...scope) {
if (this.scopes == null) {
this.scopes = new ArrayList<>();
}
this.scopes.addAll(Arrays.asList(scope));
return this;
}
/**
* 添加 扩展数据
* @param key /
* @param value /
* @return /
*/
public ApiKeyModel addExtra(String key, Object value) {
if (this.extraData == null) {
this.extraData = new LinkedHashMap<>();
}
this.extraData.put(key, value);
return this;
}
/**
* 查询扩展数据
*/
public Object getExtra(String key) {
if (this.extraData == null) {
return null;
}
return this.extraData.get(key);
}
/**
* 删除扩展数据
*/
public Object removeExtra(String key) {
if (this.extraData == null) {
return null;
}
return this.extraData.remove(key);
}
/**
* 数据自检,判断是否可以保存入库
*/
public void checkByCanSaved() {
if (SaFoxUtil.isEmpty(this.apiKey)) {
throw new ApiKeyException("ApiKey 值不可为空").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
}
if (this.loginId == null) {
throw new ApiKeyException("无效 ApiKey: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
}
if (this.createTime == 0) {
throw new ApiKeyException("请指定 createTime 创建时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
}
if (this.expiresTime == 0) {
throw new ApiKeyException("请指定 expiresTime 过期时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
}
if (this.isValid == null) {
throw new ApiKeyException("请指定 isValid 是否生效").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
}
}
/**
* 获取:此 ApiKey 的剩余有效期(秒), -1=永不过期
* @return /
*/
public long expiresIn() {
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
return SaTokenDao.NEVER_EXPIRE;
}
long s = (expiresTime - System.currentTimeMillis()) / 1000;
return s < 1 ? -2 : s;
}
/**
* 判断:此 ApiKey 是否已超时
* @return /
*/
public boolean timeExpired() {
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
return false;
}
return System.currentTimeMillis() > expiresTime;
}
// get and set
/**
* 获取 名称
*
* @return title 名称
*/
public String getTitle() {
return this.title;
}
/**
* 设置 名称
*
* @param title 名称
* @return 对象自身
*/
public ApiKeyModel setTitle(String title) {
this.title = title;
return this;
}
/**
* 获取 介绍
*
* @return intro 介绍
*/
public String getIntro() {
return this.intro;
}
/**
* 设置 介绍
*
* @param intro 介绍
* @return 对象自身
*/
public ApiKeyModel setIntro(String intro) {
this.intro = intro;
return this;
}
/**
* 获取 ApiKey 值
*
* @return apiKey ApiKey 值
*/
public String getApiKey() {
return this.apiKey;
}
/**
* 设置 ApiKey 值
*
* @param apiKey ApiKey 值
* @return 对象自身
*/
public ApiKeyModel setApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
/**
* 获取 账号 id
*
* @return loginId 账号 id
*/
public Object getLoginId() {
return this.loginId;
}
/**
* 设置 账号 id
*
* @param loginId 账号 id
* @return 对象自身
*/
public ApiKeyModel setLoginId(Object loginId) {
this.loginId = loginId;
return this;
}
/**
* 获取 ApiKey 创建时间,13位时间戳
*
* @return createTime ApiKey 创建时间,13位时间戳
*/
public long getCreateTime() {
return this.createTime;
}
/**
* 设置 ApiKey 创建时间,13位时间戳
*
* @param createTime ApiKey 创建时间,13位时间戳
* @return 对象自身
*/
public ApiKeyModel setCreateTime(long createTime) {
this.createTime = createTime;
return this;
}
/**
* 获取 ApiKey 到期时间,13位时间戳 (-1=永不过期)
*
* @return expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
*/
public long getExpiresTime() {
return this.expiresTime;
}
/**
* 设置 ApiKey 到期时间,13位时间戳 (-1=永不过期)
*
* @param expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
* @return 对象自身
*/
public ApiKeyModel setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
return this;
}
/**
* 获取 是否有效 (true=生效 false=禁用)
*
* @return /
*/
public Boolean getIsValid() {
return this.isValid;
}
/**
* 设置 是否有效 (true=生效 false=禁用)
*
* @param isValid /
* @return 对象自身
*/
public ApiKeyModel setIsValid(Boolean isValid) {
this.isValid = isValid;
return this;
}
/**
* 获取 授权范围
*
* @return scopes 授权范围
*/
public List<String> getScopes() {
return this.scopes;
}
/**
* 设置 授权范围
*
* @param scopes 授权范围
* @return 对象自身
*/
public ApiKeyModel setScopes(List<String> scopes) {
this.scopes = scopes;
return this;
}
/**
* 获取 扩展数据
*
* @return extraData 扩展数据
*/
public Map<String, Object> getExtraData() {
return this.extraData;
}
/**
* 设置 扩展数据
*
* @param extraData 扩展数据
* @return 对象自身
*/
public ApiKeyModel setExtraData(Map<String, Object> extraData) {
this.extraData = extraData;
return this;
}
@Override
public String toString() {
return "ApiKeyModel{" +
"title='" + title +
", intro='" + intro +
", apiKey='" + apiKey +
", loginId=" + loginId +
", createTime=" + createTime +
", expiresTime=" + expiresTime +
", isValid=" + isValid +
", scopes=" + scopes +
", extraData=" + extraData +
'}';
}
}
@@ -120,11 +120,8 @@ public interface SaGetValueInterface {
*/
@SuppressWarnings("unchecked")
default <T> T getModel(String key, Class<T> cs, Object defaultValue) {
Object value = get(key);
if(valueIsNull(value)) {
return (T)defaultValue;
}
return SaFoxUtil.getValueByType(value, cs);
T model = getModel(key, cs);
return valueIsNull(model) ? (T)defaultValue : model;
}
/**
@@ -16,6 +16,7 @@
package cn.dev33.satoken.application;
import cn.dev33.satoken.fun.SaRetFunction;
import cn.dev33.satoken.fun.SaRetGenericFunction;
/**
* 对写值的一组方法封装
@@ -55,7 +56,7 @@ public interface SaSetValueInterface extends SaGetValueInterface {
* @return 值
*/
@SuppressWarnings("unchecked")
default <T> T get(String key, SaRetFunction fun) {
default <T> T get(String key, SaRetGenericFunction<T> fun) {
Object value = get(key);
if(value == null) {
value = fun.run();
@@ -0,0 +1,110 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.config;
/**
* Sa-Token API Key 相关配置
*
* @author click33
* @since 1.42.0
*/
public class SaApiKeyConfig {
/**
* API Key 前缀
*/
private String prefix = "AK-";
/**
* API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
*/
private long timeout = 2592000;
/**
* 框架是否记录索引信息
*/
private Boolean isRecordIndex = true;
/**
* 获取 API Key 前缀
*
* @return /
*/
public String getPrefix() {
return this.prefix;
}
/**
* 设置 API Key 前缀
*
* @param prefix /
* @return 对象自身
*/
public SaApiKeyConfig setPrefix(String prefix) {
this.prefix = prefix;
return this;
}
/**
* 获取 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
*
* @return /
*/
public long getTimeout() {
return this.timeout;
}
/**
* 设置 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
*
* @param timeout /
* @return 对象自身
*/
public SaApiKeyConfig setTimeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* 获取 框架是否保存索引信息
*
* @return /
*/
public Boolean getIsRecordIndex() {
return this.isRecordIndex;
}
/**
* 设置 框架是否保存索引信息
*
* @param isRecordIndex /
* @return 对象自身
*/
public SaApiKeyConfig setIsRecordIndex(Boolean isRecordIndex) {
this.isRecordIndex = isRecordIndex;
return this;
}
@Override
public String toString() {
return "SaApiKeyConfig{" +
"prefix='" + prefix + '\'' +
", timeout=" + timeout +
", isRecordIndex=" + isRecordIndex +
'}';
}
}
@@ -15,6 +15,10 @@
*/
package cn.dev33.satoken.config;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.SaParamRetFunction;
import cn.dev33.satoken.secure.SaSecureUtil;
/**
* Sa-Token API 接口签名/验签 相关配置类
*
@@ -36,6 +40,10 @@ public class SaSignConfig {
*/
private long timestampDisparity = 1000 * 60 * 15;
/**
* 对 fullStr 的摘要算法
*/
private String digestAlgo = "md5";
public SaSignConfig() {
}
@@ -48,6 +56,70 @@ public class SaSignConfig {
this.secretKey = secretKey;
}
// -------------- 扩展方法
/**
* 计算保存 nonce 时应该使用的 ttl,单位:秒
* @return /
*/
public long getSaveNonceExpire() {
// 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒
if(timestampDisparity >= 0) {
return timestampDisparity / 1000;
}
// 否则,nonceTtl 的值为 24 小时
else {
return 60 * 60 * 24;
}
}
// -------------- 策略函数
/**
* 对 fullStr 的摘要算法函数
*/
public SaParamRetFunction<String, String> digestMethod = (fullStr) -> {
// md5
if(digestAlgo.equalsIgnoreCase("md5")) {
return SaSecureUtil.md5(fullStr);
}
// sha1
if(digestAlgo.equalsIgnoreCase("sha1")) {
return SaSecureUtil.sha1(fullStr);
}
// sha256
if(digestAlgo.equalsIgnoreCase("sha256")) {
return SaSecureUtil.sha256(fullStr);
}
// sha384
if(digestAlgo.equalsIgnoreCase("sha384")) {
return SaSecureUtil.sha384(fullStr);
}
// sha512
if(digestAlgo.equalsIgnoreCase("sha512")) {
return SaSecureUtil.sha512(fullStr);
}
// 未知
throw new SaTokenException("不支持的摘要算法:" + digestAlgo + ",你可以自定义摘要算法函数实现");
};
/**
* 设置: 对 fullStr 的摘要算法函数
*
* @param digestMethod /
* @return 对象自身
*/
public SaSignConfig setDigestMethod(SaParamRetFunction<String, String> digestMethod) {
this.digestMethod = digestMethod;
return this;
}
// -------------- get/set
/**
* 获取 API 调用签名秘钥
*
@@ -95,18 +167,22 @@ public class SaSignConfig {
}
/**
* 计算保存 nonce 时应该使用的 ttl,单位:秒
* 获取 对 fullStr 的摘要算法
*
* @return digestAlgo 对 fullStr 的摘要算法
*/
public String getDigestAlgo() {
return this.digestAlgo;
}
/**
* 设置 对 fullStr 的摘要算法
* @param digestAlgo /
* @return /
*/
public long getSaveNonceExpire() {
// 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒
if(timestampDisparity >= 0) {
return timestampDisparity / 1000;
}
// 否则,nonceTtl 的值为 24 小时
else {
return 60 * 60 * 24;
}
public SaSignConfig setDigestAlgo(String digestAlgo) {
this.digestAlgo = digestAlgo;
return this;
}
@Override
@@ -117,15 +193,4 @@ public class SaSignConfig {
+ "]";
}
/**
* 设置:是否校验 nonce 随机字符串
* <h2> isCheckNonce 方案已废弃,不再提供此配置项 </h2>
*
* @param isCheckNonce /
*/
@Deprecated
public void setIsCheckNonce(Boolean isCheckNonce) {
System.err.println("--------- isCheckNonce 方案已废弃,不再提供此配置项 ---------");
}
}
@@ -15,9 +15,14 @@
*/
package cn.dev33.satoken.config;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token 配置类 Model
@@ -59,13 +64,23 @@ public class SaTokenConfig implements Serializable {
/**
* 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
*/
private Boolean isShare = true;
private Boolean isShare = false;
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*/
private SaReplacedRange replacedRange = SaReplacedRange.CURR_DEVICE_TYPE;
/**
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
*/
private int maxLoginCount = 12;
/**
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*/
private SaLogoutMode overflowLogoutMode = SaLogoutMode.LOGOUT;
/**
* 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
*/
@@ -86,11 +101,38 @@ public class SaTokenConfig implements Serializable {
*/
private Boolean isReadCookie = true;
/**
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
private Boolean isLastingCookie = true;
/**
* 是否在登录后将 token 写入到响应头
*/
private Boolean isWriteHeader = false;
/**
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
*/
private SaLogoutRange logoutRange = SaLogoutRange.TOKEN;
/**
* 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
*/
private Boolean isLogoutKeepFreezeOps = false;
/**
* 在注销 token 后,是否保留其对应的 Token-Session
*/
private Boolean isLogoutKeepTokenSession = false;
/**
* 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*/
private Boolean rightNowCreateTokenSession = false;
/**
* token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik
*/
@@ -116,6 +158,11 @@ public class SaTokenConfig implements Serializable {
*/
private String tokenPrefix;
/**
* cookie 模式是否自动填充 token 前缀
*/
private Boolean cookieAutoFillPrefix = false;
/**
* 是否在初始化配置时在控制台打印版本字符画
*/
@@ -181,6 +228,15 @@ public class SaTokenConfig implements Serializable {
*/
public SaSignConfig sign = new SaSignConfig();
/**
* API 签名配置 多实例
*/
public Map<String, SaSignConfig> signMany = new LinkedHashMap<>();
/**
* API Key 相关配置
*/
public SaApiKeyConfig apiKey = new SaApiKeyConfig();
/**
* @return token 名称 (同时也是: cookie 名称、提交 token 时参数的名称、存储 token 时的 key 前缀)
@@ -360,6 +416,26 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* 获取 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*
* @return isLastingCookie /
*/
public Boolean getIsLastingCookie() {
return this.isLastingCookie;
}
/**
* 设置 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*
* @param isLastingCookie /
* @return 对象自身
*/
public SaTokenConfig setIsLastingCookie(Boolean isLastingCookie) {
this.isLastingCookie = isLastingCookie;
return this;
}
/**
* @return 是否在登录后将 token 写入到响应头
*/
@@ -455,7 +531,23 @@ public class SaTokenConfig implements Serializable {
this.tokenPrefix = tokenPrefix;
return this;
}
/**
* @return cookie 模式是否自动填充 token 前缀
*/
public Boolean getCookieAutoFillPrefix() {
return cookieAutoFillPrefix;
}
/**
* @param cookieAutoFillPrefix cookie 模式是否自动填充 token 前缀
* @return 对象自身
*/
public SaTokenConfig setCookieAutoFillPrefix(Boolean cookieAutoFillPrefix) {
this.cookieAutoFillPrefix = cookieAutoFillPrefix;
return this;
}
/**
* @return 是否在初始化配置时在控制台打印版本字符画
*/
@@ -637,7 +729,127 @@ public class SaTokenConfig implements Serializable {
this.checkSameToken = checkSameToken;
return this;
}
/**
* 获取 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
*
* @return /
*/
public SaReplacedRange getReplacedRange() {
return this.replacedRange;
}
/**
* 设置 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端 ALL_DEVICE_TYPE=所有设备类型端)
*
* @param replacedRange /
* @return 对象自身
*/
public SaTokenConfig setReplacedRange(SaReplacedRange replacedRange) {
this.replacedRange = replacedRange;
return this;
}
/**
* 获取 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*
* @return /
*/
public SaLogoutMode getOverflowLogoutMode() {
return this.overflowLogoutMode;
}
/**
* 设置 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*
* @param overflowLogoutMode /
* @return 对象自身
*/
public SaTokenConfig setOverflowLogoutMode(SaLogoutMode overflowLogoutMode) {
this.overflowLogoutMode = overflowLogoutMode;
return this;
}
/**
* 获取 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话) <br> (此参数只在调用 StpUtil.logout() 时有效)
*
* @return /
*/
public SaLogoutRange getLogoutRange() {
return this.logoutRange;
}
/**
* 设置 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话) <br> (此参数只在调用 StpUtil.logout() 时有效)
*
* @param logoutRange /
* @return 对象自身
*/
public SaTokenConfig setLogoutRange(SaLogoutRange logoutRange) {
this.logoutRange = logoutRange;
return this;
}
/**
* 获取 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API) <br> (此参数只在调用 StpUtil.[logoutkickoutreplaced]ByTokenValue("token") 时有效)
*
* @return isLogoutKeepFreezeOps /
*/
public Boolean getIsLogoutKeepFreezeOps() {
return this.isLogoutKeepFreezeOps;
}
/**
* 设置 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API) <br> (此参数只在调用 StpUtil.[logoutkickoutreplaced]ByTokenValue("token") 时有效)
*
* @param isLogoutKeepFreezeOps /
* @return 对象自身
*/
public SaTokenConfig setIsLogoutKeepFreezeOps(Boolean isLogoutKeepFreezeOps) {
this.isLogoutKeepFreezeOps = isLogoutKeepFreezeOps;
return this;
}
/**
* 获取 在注销 token 后,是否保留其对应的 Token-Session
*
* @return isLogoutKeepTokenSession /
*/
public Boolean getIsLogoutKeepTokenSession() {
return this.isLogoutKeepTokenSession;
}
/**
* 设置 在注销 token 后,是否保留其对应的 Token-Session
*
* @param isLogoutKeepTokenSession /
* @return 对象自身
*/
public SaTokenConfig setIsLogoutKeepTokenSession(Boolean isLogoutKeepTokenSession) {
this.isLogoutKeepTokenSession = isLogoutKeepTokenSession;
return this;
}
/**
* 获取 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*
* @return /
*/
public Boolean getRightNowCreateTokenSession() {
return this.rightNowCreateTokenSession;
}
/**
* 设置 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*
* @param rightNowCreateTokenSession /
* @return 对象自身
*/
public SaTokenConfig setRightNowCreateTokenSession(Boolean rightNowCreateTokenSession) {
this.rightNowCreateTokenSession = rightNowCreateTokenSession;
return this;
}
/**
* @return Cookie 全局配置对象
*/
@@ -670,6 +882,47 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* 获取 API 签名配置 多实例
*
* @return /
*/
public Map<String, SaSignConfig> getSignMany() {
return this.signMany;
}
/**
* 设置 API 签名配置 多实例
*
* @param signMany /
* @return /
*/
public SaTokenConfig setSignMany(Map<String, SaSignConfig> signMany) {
this.signMany = signMany;
return this;
}
/**
* API Key 相关配置
*
* @return /
*/
public SaApiKeyConfig getApiKey() {
return this.apiKey;
}
/**
* 设置 API Key 相关配置
*
* @param apiKey /
* @return /
*/
public SaTokenConfig setApiKey(SaApiKeyConfig apiKey) {
this.apiKey = apiKey;
return this;
}
@Override
public String toString() {
return "SaTokenConfig ["
@@ -677,19 +930,27 @@ public class SaTokenConfig implements Serializable {
+ ", timeout=" + timeout
+ ", activeTimeout=" + activeTimeout
+ ", dynamicActiveTimeout=" + dynamicActiveTimeout
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", replacedRange=" + replacedRange
+ ", maxLoginCount=" + maxLoginCount
+ ", overflowLogoutMode=" + overflowLogoutMode
+ ", maxTryTimes=" + maxTryTimes
+ ", isReadBody=" + isReadBody
+ ", isReadHeader=" + isReadHeader
+ ", isReadCookie=" + isReadCookie
+ ", isLastingCookie=" + isLastingCookie
+ ", isWriteHeader=" + isWriteHeader
+ ", logoutRange=" + logoutRange
+ ", isLogoutKeepFreezeOps=" + isLogoutKeepFreezeOps
+ ", isLogoutKeepTokenSession=" + isLogoutKeepTokenSession
+ ", rightNowCreateTokenSession=" + rightNowCreateTokenSession
+ ", tokenStyle=" + tokenStyle
+ ", dataRefreshPeriod=" + dataRefreshPeriod
+ ", tokenSessionCheckLogin=" + tokenSessionCheckLogin
+ ", autoRenew=" + autoRenew
+ ", autoRenew=" + autoRenew
+ ", tokenPrefix=" + tokenPrefix
+ ", cookieAutoFillPrefix=" + cookieAutoFillPrefix
+ ", isPrint=" + isPrint
+ ", isLog=" + isLog
+ ", logLevel=" + logLevel
@@ -703,6 +964,8 @@ public class SaTokenConfig implements Serializable {
+ ", checkSameToken=" + checkSameToken
+ ", cookie=" + cookie
+ ", sign=" + sign
+ ", signMany=" + signMany
+ ", apiKey=" + apiKey
+ "]";
}
@@ -36,7 +36,7 @@ public class SaHolder {
* @return /
*/
public static SaTokenContext getContext() {
return SaManager.getSaTokenContextOrSecond();
return SaManager.getSaTokenContext();
}
/**
@@ -46,7 +46,7 @@ public class SaHolder {
* @return /
*/
public static SaRequest getRequest() {
return SaManager.getSaTokenContextOrSecond().getRequest();
return SaManager.getSaTokenContext().getRequest();
}
/**
@@ -56,7 +56,7 @@ public class SaHolder {
* @return /
*/
public static SaResponse getResponse() {
return SaManager.getSaTokenContextOrSecond().getResponse();
return SaManager.getSaTokenContext().getResponse();
}
/**
@@ -66,7 +66,7 @@ public class SaHolder {
* @return /
*/
public static SaStorage getStorage() {
return SaManager.getSaTokenContextOrSecond().getStorage();
return SaManager.getSaTokenContext().getStorage();
}
/**
@@ -16,8 +16,9 @@
package cn.dev33.satoken.context;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
/**
* Sa-Token 上下文处理器
@@ -30,51 +31,59 @@ import cn.dev33.satoken.context.model.SaResponse;
public interface SaTokenContext {
/**
* 获取当前请求的 Request 包装对象
* 初始化上下文
*
* @param req /
* @param res /
* @param stg /
*/
void setContext(SaRequest req, SaResponse res, SaStorage stg);
/**
* 清除化上下文
*/
void clearContext();
/**
* 判断当前上下文是否可用
*
* @return /
*/
boolean isValid();
/**
* 获取 Box 对象
*/
SaTokenContextModelBox getModelBox();
/**
* 获取当前上下文的 Request 包装对象
* @see SaRequest
*
* @return /
*/
SaRequest getRequest();
default SaRequest getRequest() {
return getModelBox().getRequest();
}
/**
* 获取当前请求的 Response 包装对象
* 获取当前上下文的 Response 包装对象
* @see SaResponse
*
* @return /
*/
SaResponse getResponse();
default SaResponse getResponse(){
return getModelBox().getResponse();
}
/**
* 获取当前请求的 Storage 包装对象
* 获取当前上下文的 Storage 包装对象
* @see SaStorage
*
* @return /
*/
SaStorage getStorage();
/**
* 判断:指定路由匹配符是否可以匹配成功指定路径
* <pre>
* 判断规则由底层 web 框架决定,例如在 springboot 中:
* - matchPath("/user/*", "/user/login") 返回: true
* - matchPath("/user/*", "/article/edit") 返回: false
* </pre>
*
* @param pattern 路由匹配符
* @param path 需要匹配的路径
* @return /
*/
boolean matchPath(String pattern, String path);
/**
* 判断:在本次请求中,此上下文是否可用。
* <p> 例如在部分 rpc 调用时, 一级上下文会返回 false,这时候框架就会选择使用二级上下文来处理请求 </p>
*
* @return /
*/
default boolean isValid() {
return false;
default SaStorage getStorage(){
return getModelBox().getStorage();
}
}
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenContextException;
@@ -44,6 +45,26 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
*/
public static final String ERROR_MESSAGE = "未能获取有效的上下文处理器";
@Override
public void setContext(SaRequest req, SaResponse res, SaStorage stg) {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public void clearContext() {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public boolean isValid() {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public SaTokenContextModelBox getModelBox() {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public SaRequest getRequest() {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
@@ -59,9 +80,4 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public boolean matchPath(String pattern, String path) {
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
}
@@ -13,36 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao;
package cn.dev33.satoken.context;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
/**
* Jackson 定制版 SaSession忽略 timeout 等属性的序列化
*
* Sa-Token 上下文处理器次级实现只读上下文
*
* @author click33
* @since 1.34.0
* @since 1.42.0
*/
@JsonIgnoreProperties({"timeout"})
public class SaSessionForJacksonCustomized extends SaSession {
public interface SaTokenContextForReadOnly extends SaTokenContext {
/**
*
*/
private static final long serialVersionUID = -7600983549653130681L;
@Override
default void setContext(SaRequest req, SaResponse res, SaStorage stg) {
public SaSessionForJacksonCustomized() {
super();
}
/**
* 构建一个Session对象
* @param id Session的id
*/
public SaSessionForJacksonCustomized(String id) {
super(id);
@Override
default void clearContext() {
}
@Override
default SaTokenContextModelBox getModelBox() {
return null;
}
}
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
/**
* Sa-Token 上下文处理器 [ ThreadLocal 版本 ]
@@ -35,28 +36,23 @@ import cn.dev33.satoken.context.model.SaStorage;
public class SaTokenContextForThreadLocal implements SaTokenContext {
@Override
public SaRequest getRequest() {
return SaTokenContextForThreadLocalStorage.getRequest();
public void setContext(SaRequest req, SaResponse res, SaStorage stg) {
SaTokenContextForThreadLocalStaff.setModelBox(req, res, stg);
}
@Override
public SaResponse getResponse() {
return SaTokenContextForThreadLocalStorage.getResponse();
}
@Override
public SaStorage getStorage() {
return SaTokenContextForThreadLocalStorage.getStorage();
}
@Override
public boolean matchPath(String pattern, String path) {
return false;
public void clearContext() {
SaTokenContextForThreadLocalStaff.clearModelBox();
}
@Override
public boolean isValid() {
return SaTokenContextForThreadLocalStorage.getBox() != null;
return SaTokenContextForThreadLocalStaff.getModelBoxOrNull() != null;
}
@Override
public SaTokenContextModelBox getModelBox() {
return SaTokenContextForThreadLocalStaff.getModelBox();
}
}
@@ -18,6 +18,7 @@ package cn.dev33.satoken.context;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.model.SaTokenContextModelBox;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenContextException;
@@ -29,12 +30,12 @@ import cn.dev33.satoken.exception.SaTokenContextException;
* @author click33
* @since 1.16.0
*/
public class SaTokenContextForThreadLocalStorage {
public class SaTokenContextForThreadLocalStaff {
/**
* 基于 ThreadLocal [ Box 存储器 ]
*/
public static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<>();
public static ThreadLocal<SaTokenContextModelBox> modelBoxThreadLocal = new ThreadLocal<>();
/**
* 初始化当前线程的 [ Box 存储器 ]
@@ -42,34 +43,34 @@ public class SaTokenContextForThreadLocalStorage {
* @param response {@link SaResponse}
* @param storage {@link SaStorage}
*/
public static void setBox(SaRequest request, SaResponse response, SaStorage storage) {
Box bok = new Box(request, response, storage);
boxThreadLocal.set(bok);
public static void setModelBox(SaRequest request, SaResponse response, SaStorage storage) {
SaTokenContextModelBox bok = new SaTokenContextModelBox(request, response, storage);
modelBoxThreadLocal.set(bok);
}
/**
* 清除当前线程的 [ Box 存储器 ]
*/
public static void clearBox() {
boxThreadLocal.remove();
public static void clearModelBox() {
modelBoxThreadLocal.remove();
}
/**
* 获取当前线程的 [ Box 存储器 ]
* @return /
*/
public static Box getBox() {
return boxThreadLocal.get();
public static SaTokenContextModelBox getModelBoxOrNull() {
return modelBoxThreadLocal.get();
}
/**
* 获取当前线程的 [ Box 存储器 ], 如果为空则抛出异常
* @return /
*/
public static Box getBoxNotNull() {
Box box = boxThreadLocal.get();
public static SaTokenContextModelBox getModelBox() {
SaTokenContextModelBox box = modelBoxThreadLocal.get();
if(box == null) {
throw new SaTokenContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
throw new SaTokenContextException("SaTokenContext 上下文尚未初始化").setCode(SaErrorCode.CODE_10002);
}
return box;
}
@@ -80,7 +81,7 @@ public class SaTokenContextForThreadLocalStorage {
* @return /
*/
public static SaRequest getRequest() {
return getBoxNotNull().getRequest();
return getModelBox().getRequest();
}
/**
@@ -89,7 +90,7 @@ public class SaTokenContextForThreadLocalStorage {
* @return /
*/
public static SaResponse getResponse() {
return getBoxNotNull().getResponse();
return getModelBox().getResponse();
}
/**
@@ -98,59 +99,8 @@ public class SaTokenContextForThreadLocalStorage {
* @return /
*/
public static SaStorage getStorage() {
return getBoxNotNull().getStorage();
return getModelBox().getStorage();
}
/**
* Box 临时内部类用于存储 [ SaRequestSaResponseSaStorage ] 三个包装对象
*
* @author click33
* @since 1.16.0
*/
public static class Box {
public SaRequest request;
public SaResponse response;
public SaStorage storage;
public Box(SaRequest request, SaResponse response, SaStorage storage){
this.request = request;
this.response = response;
this.storage = storage;
}
public SaRequest getRequest() {
return request;
}
public void setRequest(SaRequest request) {
this.request = request;
}
public SaResponse getResponse() {
return response;
}
public void setResponse(SaResponse response) {
this.response = response;
}
public SaStorage getStorage() {
return storage;
}
public void setStorage(SaStorage storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Box [request=" + request + ", response=" + response + ", storage=" + storage + "]";
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.mock;
import cn.dev33.satoken.application.ApplicationInfo;
import cn.dev33.satoken.context.model.SaRequest;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 对 SaRequest 包装类的实现(Mock 版)
*
* @author click33
* @since 1.42.0
*/
public class SaRequestForMock implements SaRequest {
public Map<String, String> parameterMap = new LinkedHashMap<>();
public Map<String, String> headerMap = new LinkedHashMap<>();
public Map<String, String> cookieMap = new LinkedHashMap<>();
public String requestPath;
public String url;
public String method;
public String host;
public String forwardTo;
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return null;
}
/**
* 在 [请求体] 里获取一个值
*/
@Override
public String getParam(String name) {
return parameterMap.get(name);
}
/**
* 获取 [请求体] 里提交的所有参数名称
* @return 参数名称列表
*/
@Override
public Collection<String> getParamNames(){
return parameterMap.keySet();
}
/**
* 获取 [请求体] 里提交的所有参数
* @return 参数列表
*/
@Override
public Map<String, String> getParamMap(){
return parameterMap;
}
/**
* 在 [请求头] 里获取一个值
*/
@Override
public String getHeader(String name) {
return headerMap.get(name);
}
/**
* 在 [Cookie作用域] 里获取一个值
*/
@Override
public String getCookieValue(String name) {
return getCookieLastValue(name);
}
/**
* 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的)
*/
@Override
public String getCookieFirstValue(String name){
return cookieMap.get(name);
}
/**
* 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的)
* @param name 键
* @return 值
*/
@Override
public String getCookieLastValue(String name){
return cookieMap.get(name);
}
/**
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestPath() {
return ApplicationInfo.cutPathPrefix(requestPath);
}
/**
* 返回当前请求的url,例:http://xxx.com/test
* @return see note
*/
public String getUrl() {
return url;
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return method;
}
/**
* 查询请求 host
*/
@Override
public String getHost() {
return host;
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
this.forwardTo = path;
return null;
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.mock;
import cn.dev33.satoken.context.model.SaResponse;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 对 SaResponse 包装类的实现(Mock 版)
*
* @author click33
* @since 1.42.0
*/
public class SaResponseForMock implements SaResponse {
public int status;
public Map<String, String> headerMap = new LinkedHashMap<>();
public String redirectTo;
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return null;
}
/**
* 设置响应状态码
*/
@Override
public SaResponse setStatus(int sc) {
this.status = sc;
return this;
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
headerMap.put(name, value);
return this;
}
/**
* 在响应头里添加一个值
* @param name 名字
* @param value 值
* @return 对象自身
*/
public SaResponse addHeader(String name, String value) {
headerMap.put(name, value);
return this;
}
/**
* 重定向
*/
@Override
public Object redirect(String url) {
this.redirectTo = url;
return null;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.mock;
import cn.dev33.satoken.context.model.SaStorage;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 对 SaStorage 包装类的实现(Mock 版)
*
* @author click33
* @since 1.42.0
*/
public class SaStorageForMock implements SaStorage {
public Map<String, Object> dataMap = new LinkedHashMap<>();
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return null;
}
/**
* 在 [Request作用域] 里写入一个值
*/
@Override
public SaStorageForMock set(String key, Object value) {
dataMap.put(key, value);
return this;
}
/**
* 在 [Request作用域] 里获取一个值
*/
@Override
public Object get(String key) {
return dataMap.get(key);
}
/**
* 在 [Request作用域] 里删除一个值
*/
@Override
public SaStorageForMock delete(String key) {
dataMap.remove(key);
return this;
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.mock;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaRetGenericFunction;
/**
* Sa-Token Mock 上下文 操作工具类
*
* @author click33
* @since 1.42.0
*/
public class SaTokenContextMockUtil {
/**
* 写入 Mock 上下文
*/
public static void setMockContext() {
SaRequestForMock request = new SaRequestForMock();
SaResponseForMock response = new SaResponseForMock();
SaStorageForMock storage = new SaStorageForMock();
SaManager.getSaTokenContext().setContext(request, response, storage);
}
/**
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
*
* @param fun /
*/
public static void setMockContext(SaFunction fun) {
try {
setMockContext();
fun.run();
} finally {
clearContext();
}
}
/**
* 写入 Mock 上下文,并执行一段代码,执行完毕后清除上下文
*
* @param fun /
*/
public static <T> T setMockContext(SaRetGenericFunction<T> fun) {
try {
setMockContext();
return fun.run();
} finally {
clearContext();
}
}
/**
* 清除上下文
*/
public static void clearContext() {
SaManager.getSaTokenContext().clearContext();
}
}
@@ -21,7 +21,6 @@ import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
@@ -191,6 +190,12 @@ public interface SaRequest {
return getMethod().equals(method.name());
}
/**
* 查询请求 host
* @return /
*/
String getHost();
/**
* 判断此请求是否为 Ajax 异步请求
* @return /
@@ -0,0 +1,52 @@
package cn.dev33.satoken.context.model;
/**
* Box 盒子类,用于存储 [ SaRequest、SaResponse、SaStorage ] 三个包装对象
*
* @author click33
* @since 1.16.0
*/
public class SaTokenContextModelBox {
public SaRequest request;
public SaResponse response;
public SaStorage storage;
public SaTokenContextModelBox(SaRequest request, SaResponse response, SaStorage storage) {
this.request = request;
this.response = response;
this.storage = storage;
}
public SaRequest getRequest() {
return request;
}
public void setRequest(SaRequest request) {
this.request = request;
}
public SaResponse getResponse() {
return response;
}
public void setResponse(SaResponse response) {
this.response = response;
}
public SaStorage getStorage() {
return storage;
}
public void setStorage(SaStorage storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Box [request=" + request + ", response=" + response + ", storage=" + storage + "]";
}
}
@@ -90,15 +90,25 @@ public interface SaTokenDao {
/**
* 获取 Object,如无返空
* @param key 键名称
*
* @param key 键名称
* @return object
*/
Object getObject(String key);
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
<T> T getObject(String key, Class<T> classType);
/**
* 写入 Object,并设定存活时间 (单位: 秒)
* @param key 键名称
* @param object 值
*
* @param key 键名称
* @param object 值
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
*/
void setObject(String key, Object object, long timeout);
@@ -138,52 +148,40 @@ public interface SaTokenDao {
* @param sessionId sessionId
* @return SaSession
*/
default SaSession getSession(String sessionId) {
return (SaSession)getObject(sessionId);
}
SaSession getSession(String sessionId);
/**
* 写入 SaSession,并设定存活时间(单位: 秒)
* @param session 要保存的 SaSession 对象
* @param timeout 过期时间(单位: 秒)
*/
default void setSession(SaSession session, long timeout) {
setObject(session.getId(), session, timeout);
}
void setSession(SaSession session, long timeout);
/**
* 更新 SaSession
* @param session 要更新的 SaSession 对象
*/
default void updateSession(SaSession session) {
updateObject(session.getId(), session);
}
void updateSession(SaSession session);
/**
* 删除 SaSession
* @param sessionId sessionId
*/
default void deleteSession(String sessionId) {
deleteObject(sessionId);
}
void deleteSession(String sessionId);
/**
* 获取 SaSession 剩余存活时间(单位: 秒)
* @param sessionId 指定 SaSession
* @return 这个 SaSession 的剩余存活时间
*/
default long getSessionTimeout(String sessionId) {
return getObjectTimeout(sessionId);
}
long getSessionTimeout(String sessionId);
/**
* 修改 SaSession 剩余存活时间(单位: 秒)
* @param sessionId 指定 SaSession
* @param timeout 剩余存活时间
*/
default void updateSessionTimeout(String sessionId, long timeout) {
updateObjectTimeout(sessionId, timeout);
}
void updateSessionTimeout(String sessionId, long timeout);
// --------------------- 会话管理 ---------------------
@@ -16,250 +16,81 @@
package cn.dev33.satoken.dao;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.auto.SaTokenDaoByStringFollowObject;
import cn.dev33.satoken.dao.timedcache.SaMapPackageForConcurrentHashMap;
import cn.dev33.satoken.dao.timedcache.SaTimedCache;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Sa-Token 持久层接口,默认实现类基于内存 Map,系统重启后数据丢失)
* Sa-Token 持久层接口,默认实现类基于 SaTimedCache - ConcurrentHashMap (内存缓存,系统重启后数据丢失)
*
* @author click33
* @since 1.10.0
*/
public class SaTokenDaoDefaultImpl implements SaTokenDao {
/**
* 存储数据的集合
*/
public Map<String, Object> dataMap = new ConcurrentHashMap<>();
/**
* 存储数据过期时间的集合(单位: 毫秒), 记录所有 key 的到期时间 (注意存储的是到期时间,不是剩余存活时间)
*/
public Map<String, Long> expireMap = new ConcurrentHashMap<>();
// ------------------------ String 读写操作
@Override
public String get(String key) {
clearKeyByTimeout(key);
return (String)dataMap.get(key);
}
@Override
public void set(String key, String value, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, value);
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
@Override
public void update(String key, String value) {
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, value);
}
@Override
public void delete(String key) {
dataMap.remove(key);
expireMap.remove(key);
}
@Override
public long getTimeout(String key) {
return getKeyTimeout(key);
}
@Override
public void updateTimeout(String key, long timeout) {
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
public class SaTokenDaoDefaultImpl implements SaTokenDaoByStringFollowObject {
public SaTimedCache timedCache = new SaTimedCache(
new SaMapPackageForConcurrentHashMap<>(),
new SaMapPackageForConcurrentHashMap<>()
);
// ------------------------ Object 读写操作
@Override
public Object getObject(String key) {
clearKeyByTimeout(key);
return dataMap.get(key);
return timedCache.getObject(key);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getObject(String key, Class<T> classType){
return (T) getObject(key);
}
@Override
public void setObject(String key, Object object, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, object);
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
timedCache.setObject(key, object, timeout);
}
@Override
public void updateObject(String key, Object object) {
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, object);
timedCache.updateObject(key, object);
}
@Override
public void deleteObject(String key) {
dataMap.remove(key);
expireMap.remove(key);
timedCache.deleteObject(key);
}
@Override
public long getObjectTimeout(String key) {
return getKeyTimeout(key);
return timedCache.getObjectTimeout(key);
}
@Override
public void updateObjectTimeout(String key, long timeout) {
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
timedCache.updateObjectTimeout(key, timeout);
}
// ------------------------ Session 读写操作
// 使用接口默认实现
// --------- 会话管理
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
return SaFoxUtil.searchList(expireMap.keySet(), prefix, keyword, start, size, sortType);
return SaFoxUtil.searchList(timedCache.keySet(), prefix, keyword, start, size, sortType);
}
// ------------------------ 以下是一个定时缓存的简单实现,采用:惰性检查 + 异步循环扫描
// --------- 过期时间相关操作
/**
* 如果指定的 key 已经过期,则立即清除它
* @param key 指定 key
*/
void clearKeyByTimeout(String key) {
Long expirationTime = expireMap.get(key);
// 清除条件:
// 1、数据存在。
// 2、不是 [ 永不过期 ]。
// 3、已经超过过期时间。
if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {
dataMap.remove(key);
expireMap.remove(key);
}
}
/**
* 获取指定 key 的剩余存活时间 (单位:秒)
* @param key 指定 key
* @return 这个 key 的剩余存活时间
*/
long getKeyTimeout(String key) {
// 由于数据过期检测属于惰性扫描,很可能此时这个 key 已经是过期状态了,所以这里需要先检查一下
clearKeyByTimeout(key);
// 获取这个 key 的过期时间
Long expire = expireMap.get(key);
// 如果 expire 数据不存在,说明框架没有存储这个 key,此时返回 NOT_VALUE_EXPIRE
if(expire == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 如果 expire 被标注为永不过期,则返回 NEVER_EXPIRE
if(expire == SaTokenDao.NEVER_EXPIRE) {
return SaTokenDao.NEVER_EXPIRE;
}
// ---- 代码至此,说明这个 key 是有过期时间的,且未过期,那么:
// 计算剩余时间并返回 (过期时间戳 - 当前时间戳) / 1000 转秒
long timeout = (expire - System.currentTimeMillis()) / 1000;
// 小于零时,视为不存在
if(timeout < 0) {
dataMap.remove(key);
expireMap.remove(key);
return SaTokenDao.NOT_VALUE_EXPIRE;
}
return timeout;
}
// --------- 定时清理过期数据
/**
* 执行数据清理的线程引用
*/
public Thread refreshThread;
/**
* 是否继续执行数据清理的线程标记
*/
public volatile boolean refreshFlag;
/**
* 清理所有已经过期的 key
*/
public void refreshDataMap() {
for (String s : expireMap.keySet()) {
clearKeyByTimeout(s);
}
}
/**
* 初始化定时任务,定时清理过期数据
*/
public void initRefreshThread() {
// 如果开发者配置了 <=0 的值,则不启动定时清理
if(SaManager.getConfig().getDataRefreshPeriod() <= 0) {
return;
}
// 启动定时刷新
this.refreshFlag = true;
this.refreshThread = new Thread(() -> {
for (;;) {
try {
try {
// 如果已经被标记为结束
if( ! refreshFlag) {
return;
}
// 执行清理
refreshDataMap();
} catch (Exception e) {
e.printStackTrace();
}
// 休眠N秒
int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
if(dataRefreshPeriod <= 0) {
dataRefreshPeriod = 1;
}
Thread.sleep(dataRefreshPeriod * 1000L);
} catch (Exception e) {
e.printStackTrace();
}
}
});
this.refreshThread.start();
}
// --------- 组件生命周期
/**
* 组件被安装时,开始刷新数据线程
*/
@Override
public void init() {
initRefreshThread();
timedCache.initRefreshThread();
}
/**
@@ -267,6 +98,6 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
*/
@Override
public void destroy() {
this.refreshFlag = false;
timedCache.endRefreshThread();
}
}
@@ -0,0 +1,106 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.auto;
import cn.dev33.satoken.SaManager;
/**
* SaTokenDao 次级实现,Object 读写跟随 String 读写 (推荐中间件型缓存实现 implements 此接口)
*
* @author click33
* @since 1.41.0
*/
public interface SaTokenDaoByObjectFollowString extends SaTokenDaoBySessionFollowObject {
// --------------------- Object 读写 ---------------------
/**
* 获取 Object,如无返空
*
* @param key 键名称
* @return object
*/
@Override
default Object getObject(String key) {
String jsonString = get(key);
return SaManager.getSaSerializerTemplate().stringToObject(jsonString);
}
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
default <T> T getObject(String key, Class<T> classType) {
String jsonString = get(key);
return SaManager.getSaSerializerTemplate().stringToObject(jsonString, classType);
}
/**
* 写入 Object,并设定存活时间 (单位: 秒)
*
* @param key 键名称
* @param object 值
* @param timeout 存活时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
*/
@Override
default void setObject(String key, Object object, long timeout) {
String jsonString = SaManager.getSaSerializerTemplate().objectToString(object);
set(key, jsonString, timeout);
}
/**
* 更新 Object (过期时间不变)
* @param key 键名称
* @param object 值
*/
@Override
default void updateObject(String key, Object object) {
String jsonString = SaManager.getSaSerializerTemplate().objectToString(object);
update(key, jsonString);
}
/**
* 删除 Object
* @param key 键名称
*/
@Override
default void deleteObject(String key) {
delete(key);
}
/**
* 获取 Object 的剩余存活时间 (单位: 秒)
* @param key 指定 key
* @return 这个 key 的剩余存活时间
*/
@Override
default long getObjectTimeout(String key) {
return getTimeout(key);
}
/**
* 修改 Object 的剩余存活时间(单位: 秒)
* @param key 指定 key
* @param timeout 剩余存活时间
*/
@Override
default void updateObjectTimeout(String key, long timeout) {
updateTimeout(key, timeout);
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.auto;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
/**
* SaTokenDao 次级实现:SaSession 读写跟随 Object 读写
*
* @author click33
* @since 1.41.0
*/
public interface SaTokenDaoBySessionFollowObject extends SaTokenDao {
// --------------------- SaSession 读写 (默认复用 Object 读写方法) ---------------------
/**
* 获取 SaSession,如无返空
* @param sessionId sessionId
* @return SaSession
*/
default SaSession getSession(String sessionId) {
return getObject(sessionId, SaStrategy.instance.sessionClassType);
}
/**
* 写入 SaSession,并设定存活时间(单位: 秒)
* @param session 要保存的 SaSession 对象
* @param timeout 过期时间(单位: 秒)
*/
default void setSession(SaSession session, long timeout) {
setObject(session.getId(), session, timeout);
}
/**
* 更新 SaSession
* @param session 要更新的 SaSession 对象
*/
default void updateSession(SaSession session) {
updateObject(session.getId(), session);
}
/**
* 删除 SaSession
* @param sessionId sessionId
*/
default void deleteSession(String sessionId) {
deleteObject(sessionId);
}
/**
* 获取 SaSession 剩余存活时间(单位: 秒)
* @param sessionId 指定 SaSession
* @return 这个 SaSession 的剩余存活时间
*/
default long getSessionTimeout(String sessionId) {
return getObjectTimeout(sessionId);
}
/**
* 修改 SaSession 剩余存活时间(单位: 秒)
* @param sessionId 指定 SaSession
* @param timeout 剩余存活时间
*/
default void updateSessionTimeout(String sessionId, long timeout) {
updateObjectTimeout(sessionId, timeout);
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.auto;
/**
* SaTokenDao 次级实现:String 读写跟随 Object 读写 (推荐内存型缓存实现 implements 此接口)
*
* @author click33
* @since 1.41.0
*/
public interface SaTokenDaoByStringFollowObject extends SaTokenDaoBySessionFollowObject {
// --------------------- String 读写 ---------------------
@Override
default String get(String key) {
return (String) getObject(key);
}
@Override
default void set(String key, String value, long timeout) {
setObject(key, value, timeout);
}
@Override
default void update(String key, String value) {
updateObject(key, value);
}
@Override
default void delete(String key) {
deleteObject(key);
}
@Override
default long getTimeout(String key) {
return getObjectTimeout(key);
}
@Override
default void updateTimeout(String key, long timeout) {
updateObjectTimeout(key, timeout);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.timedcache;
import java.util.Set;
/**
* Map 包装类
*
* @author click33
* @since 1.41.0
*/
public interface SaMapPackage<V> {
/**
* 获取底层被包装的源对象
*
* @return /
*/
Object getSource();
/**
* 读
*
* @param key /
* @return /
*/
V get(String key);
/**
* 写
*
* @param key /
* @param value /
*/
void put(String key, V value);
/**
* 删
* @param key /
*/
void remove(String key);
/**
* 所有 key
*/
Set<String> keySet();
}
@@ -0,0 +1,56 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.timedcache;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Map 包装类 (ConcurrentHashMap 版)
*
* @author click33
* @since 1.41.0
*/
public class SaMapPackageForConcurrentHashMap<V> implements SaMapPackage<V> {
private final ConcurrentHashMap<String, V> map = new ConcurrentHashMap<String, V>();
@Override
public Object getSource() {
return map;
}
@Override
public V get(String key) {
return map.get(key);
}
@Override
public void put(String key, V value) {
map.put(key, value);
}
@Override
public void remove(String key) {
map.remove(key);
}
@Override
public Set<String> keySet() {
return map.keySet();
}
}
@@ -0,0 +1,209 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao.timedcache;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import java.util.Set;
/**
* 一个定时缓存的简单实现,采用:惰性检查 + 异步循环扫描
*
* @author click33
* @since 1.41.0
*/
public class SaTimedCache {
/**
* 存储数据的集合
*/
public SaMapPackage<Object> dataMap;
/**
* 存储数据过期时间的集合(单位: 毫秒), 记录所有 key 的到期时间 (注意存储的是到期时间,不是剩余存活时间)
*/
public SaMapPackage<Long> expireMap;
public SaTimedCache(SaMapPackage<Object> dataMap, SaMapPackage<Long> expireMap) {
this.dataMap = dataMap;
this.expireMap = expireMap;
}
// ------------------------ 基础 API 读写操作
public Object getObject(String key) {
clearKeyByTimeout(key);
return dataMap.get(key);
}
public void setObject(String key, Object object, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, object);
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
public void updateObject(String key, Object object) {
if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
dataMap.put(key, object);
}
public void deleteObject(String key) {
dataMap.remove(key);
expireMap.remove(key);
}
public long getObjectTimeout(String key) {
return getKeyTimeout(key);
}
public void updateObjectTimeout(String key, long timeout) {
expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
}
public Set<String> keySet() {
return dataMap.keySet();
}
// --------- 过期时间相关操作
/**
* 如果指定的 key 已经过期,则立即清除它
* @param key 指定 key
*/
void clearKeyByTimeout(String key) {
Long expirationTime = expireMap.get(key);
// 清除条件:
// 1、数据存在。
// 2、不是 [ 永不过期 ]。
// 3、已经超过过期时间。
if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {
dataMap.remove(key);
expireMap.remove(key);
}
}
/**
* 获取指定 key 的剩余存活时间 (单位:秒)
* @param key 指定 key
* @return 这个 key 的剩余存活时间
*/
long getKeyTimeout(String key) {
// 由于数据过期检测属于惰性扫描,很可能此时这个 key 已经是过期状态了,所以这里需要先检查一下
clearKeyByTimeout(key);
// 获取这个 key 的过期时间
Long expire = expireMap.get(key);
// 如果 expire 数据不存在,说明框架没有存储这个 key,此时返回 NOT_VALUE_EXPIRE
if(expire == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 如果 expire 被标注为永不过期,则返回 NEVER_EXPIRE
if(expire == SaTokenDao.NEVER_EXPIRE) {
return SaTokenDao.NEVER_EXPIRE;
}
// ---- 代码至此,说明这个 key 是有过期时间的,且未过期,那么:
// 计算剩余时间并返回 (过期时间戳 - 当前时间戳) / 1000 转秒
long timeout = (expire - System.currentTimeMillis()) / 1000;
// 小于零时,视为不存在
if(timeout < 0) {
dataMap.remove(key);
expireMap.remove(key);
return SaTokenDao.NOT_VALUE_EXPIRE;
}
return timeout;
}
// --------- 定时清理过期数据
/**
* 执行数据清理的线程引用
*/
public Thread refreshThread;
/**
* 是否继续执行数据清理的线程标记
*/
public volatile boolean refreshFlag;
/**
* 清理所有已经过期的 key
*/
public void refreshDataMap() {
for (String s : expireMap.keySet()) {
clearKeyByTimeout(s);
}
}
/**
* 初始化定时任务,定时清理过期数据
*/
public void initRefreshThread() {
// 如果开发者配置了 <=0 的值,则不启动定时清理
if(SaManager.getConfig().getDataRefreshPeriod() <= 0) {
return;
}
// 启动定时刷新
this.refreshFlag = true;
this.refreshThread = new Thread(() -> {
for (;;) {
try {
try {
// 如果已经被标记为结束
if( ! refreshFlag) {
return;
}
// 执行清理
refreshDataMap();
} catch (Exception e) {
e.printStackTrace();
}
// 休眠N秒
int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
if(dataRefreshPeriod <= 0) {
dataRefreshPeriod = 1;
}
Thread.sleep(dataRefreshPeriod * 1000L);
} catch (Exception e) {
e.printStackTrace();
}
}
});
this.refreshThread.start();
}
/**
* 结束定时任务
*/
public void endRefreshThread() {
this.refreshFlag = false;
}
}
@@ -128,6 +128,9 @@ public interface SaErrorCode {
/** 获取 Token-Session 时提供的 token 为空 */
int CODE_11073 = 11073;
/** 获取 Token-Session 时提供的 token 为无效 token */
int CODE_11074 = 11074;
// ------------
@@ -198,4 +201,35 @@ public interface SaErrorCode {
/** timestamp 超出允许的范围 */
int CODE_12203 = 12203;
/** 未找到对应 appid 的 SaSignConfig */
int CODE_12211 = 12211;
// ------------
/** 无效 API Key */
int CODE_12301 = 12301;
/** API Key 已过期 */
int CODE_12302 = 12302;
/** API Key 已被禁用 */
int CODE_12303 = 12303;
/** API Key 字段自检未通过 */
int CODE_12304 = 12304;
/** 未开启索引记录功能却调用了相关 API */
int CODE_12305 = 12305;
/** API Key 不具有指定 Scope */
int CODE_12311 = 12311;
/** API Key 不属于指定用户 */
int CODE_12312 = 12312;
// ------------
/** 未实现具体的路由匹配策略 */
int CODE_12401 = 12401;
}
@@ -0,0 +1,73 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 ApiKey 相关错误
*
* @author click33
* @since 1.42.0
*/
public class ApiKeyException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 ApiKey 相关错误
* @param cause 根异常原因
*/
public ApiKeyException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 ApiKey 相关错误
* @param message 异常描述
*/
public ApiKeyException(String message) {
super(message);
}
/**
* 具体引起异常的 ApiKey 值
*/
public String apiKey;
public String getApiKey() {
return apiKey;
}
public ApiKeyException setApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
/**
* 如果 flag==true,则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new ApiKeyException(message).setCode(code);
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 ApiKey Scope 相关错误
*
* @author click33
* @since 1.42.0
*/
public class ApiKeyScopeException extends ApiKeyException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 ApiKey Scope 相关错误
* @param cause 根异常原因
*/
public ApiKeyScopeException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 ApiKey Scope 相关错误
* @param message 异常描述
*/
public ApiKeyScopeException(String message) {
super(message);
}
/**
* 具体引起异常的 ApiKey 值
*/
public String apiKey;
/**
* 具体引起异常的 scope 值
*/
public String scope;
public String getApiKey() {
return apiKey;
}
public ApiKeyScopeException setApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
public String getScope() {
return scope;
}
public ApiKeyScopeException setScope(String scope) {
this.scope = scope;
return this;
}
/**
* 如果 flag==true,则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new ApiKeyScopeException(message).setCode(code);
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表防火墙检验未通过
*
* @author click33
* @since 1.41.0
*/
public class FirewallCheckException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 8243974276159004739L;
public FirewallCheckException(String message) {
super(message);
}
public FirewallCheckException(Throwable e) {
super(e);
}
public FirewallCheckException(String message, Throwable e) {
super(message, e);
}
}
@@ -21,7 +21,7 @@ package cn.dev33.satoken.exception;
* @author click33
* @since 1.37.0
*/
public class RequestPathInvalidException extends SaTokenException {
public class RequestPathInvalidException extends FirewallCheckException {
/**
* 序列化版本号
@@ -0,0 +1,58 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表插件安装过程中出现异常
*
* @author click33
* @since 1.28.0
*/
public class SaTokenPluginException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130131L;
/**
* 一个异常:代表插件安装过程中出现异常
* @param message 异常描述
*/
public SaTokenPluginException(String message) {
super(message);
}
/**
* 一个异常:代表插件安装过程中出现异常
*
* @param cause 异常对象
*/
public SaTokenPluginException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表插件安装过程中出现异常
*
* @param message 异常描述
* @param cause 异常对象
*/
public SaTokenPluginException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 TOTP 校验未通过
*
* @author click33
* @since 1.41.0
*/
public class TotpAuthException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/** 异常提示语 */
public static final String BE_MESSAGE = "totp check fail";
/**
* 一个异常:代表会话未通过 Totp 校验
*/
public TotpAuthException() {
super(BE_MESSAGE);
}
}
@@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.second;
package cn.dev33.satoken.fun;
/**
* Sa-Token 二级Context - 创建器
*
* 无形参有返回值(泛型)的函数式接口方便开发者进行 lambda 表达式风格调用
*
* @author click33
* @since 1.28.0
* @since 1.42.0
*/
@FunctionalInterface
public interface SaTokenSecondContextCreator {
public interface SaRetGenericFunction<T> {
/**
* 创建一个二级 Context 处理器
* @return /
* 执行的方法
* @return 返回值
*/
SaTokenSecondContext create();
T run();
}
@@ -13,20 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.second;
import cn.dev33.satoken.context.SaTokenContext;
package cn.dev33.satoken.fun;
/**
* Sa-Token 二级Context - 基础接口
*
* <p> (利用继承机制实现区别 [ 一级Context ] [ 二级Context ] 的目的)
* 双形参无返回值的函数式接口方便开发者进行 lambda 表达式风格调用
*
* @see SaTokenContext SaTokenContext 上下文处理器
*
* @author click33
* @since 1.28.0
* @since 1.41.0
*/
public interface SaTokenSecondContext extends SaTokenContext {
@FunctionalInterface
public interface SaTwoParamFunction<T, T2> {
/**
* 执行的方法
* @param r 传入的参数
* @param r2 传入的参数 2
*/
void run(T r, T2 r2);
}
@@ -0,0 +1,34 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.fun.hooks;
import cn.dev33.satoken.plugin.SaTokenPlugin;
/**
* SaTokenPlugin 钩子函数
*
* @author click33
* @since 1.41.0
*/
@FunctionalInterface
public interface SaTokenPluginHookFunction<T extends SaTokenPlugin> {
/**
* 执行的方法
* @param plugin 插件实例
*/
void execute(SaTokenPlugin plugin);
}
@@ -13,25 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.dao;
package cn.dev33.satoken.fun.strategy;
import org.noear.redisx.RedisClient;
import cn.dev33.satoken.stp.StpLogic;
import java.util.Properties;
import java.util.function.Function;
/**
* SaTokenDao redis 适配
* 函数式接口自定义自动续期条件
*
* @author noear
* @since 1.34.0
* <p> 参数StpLogic 实例 </p>
* <p> 返回Boolean 是否续期 </p>
*
* @author fangzhengjin
* @since 1.41.0
*/
public class SaTokenDaoOfRedis extends SaTokenDaoOfRedisBase64 {
public SaTokenDaoOfRedis(Properties props) {
super(props);
}
public SaTokenDaoOfRedis(RedisClient redisClient) {
super(redisClient);
}
}
@FunctionalInterface
public interface SaAutoRenewFunction extends Function<StpLogic, Boolean> {
}
@@ -0,0 +1,44 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.fun.strategy;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
/**
* CORS 跨域策略处理函数
*
* @author click33
* @since 1.42.0
*/
@FunctionalInterface
public interface SaCorsHandleFunction {
/**
* CORS 策略处理函数
*
* @param req 请求包装对象
* @param res 响应包装对象
* @param sto 数据读写对象
*/
void execute(
SaRequest req,
SaResponse res,
SaStorage sto
);
}
@@ -15,25 +15,26 @@
*/
package cn.dev33.satoken.fun.strategy;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.FirewallCheckException;
/**
* 函数式接口校验请求 path 的算法
*
* <p> 如果属于无效请求 path则抛出异常 RequestPathInvalidException </p>
* 函数式接口当防火墙校验不通过时执行的函数
*
* @author click33
* @since 1.37.0
*/
@FunctionalInterface
public interface SaCheckRequestPathFunction {
public interface SaFirewallCheckFailHandleFunction {
/**
* 执行函数
* @param path 请求 path
* @param extArg1 扩展参数1
* @param extArg2 扩展参数2
* @param e 防火墙校验异常
* @param req 请求对象
* @param res 响应对象
* @param extArg 预留扩展参数
*/
void run(String path, Object extArg1, Object extArg2);
void run(FirewallCheckException e, SaRequest req, SaResponse res, Object extArg);
}
@@ -15,23 +15,25 @@
*/
package cn.dev33.satoken.fun.strategy;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
/**
* 函数式接口当请求 path 校验不通过时处理方案的算法
* 函数式接口防火墙校验函数
*
* @author click33
* @since 1.37.0
*/
@FunctionalInterface
public interface SaRequestPathInvalidHandleFunction {
public interface SaFirewallCheckFunction {
/**
* 执行函数
* @param e 请求 path 无效的异常对象
* @param extArg1 扩展参数1
* @param extArg2 扩展参数2
*
* @param req 请求对象
* @param res 响应对象
* @param extArg 预留扩展参数
*/
void run(RequestPathInvalidException e, Object extArg1, Object extArg2);
void execute(SaRequest req, SaResponse res, Object extArg);
}
@@ -0,0 +1,32 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.fun.strategy;
import java.util.function.BiFunction;
/**
* 函数式接口:路由匹配策略
*
* <p> 参数:pattern, path </p>
* <p> 返回:是否匹配 </p>
*
* @author click33
* @since 1.42.0
*/
@FunctionalInterface
public interface SaRouteMatchFunction extends BiFunction<String, String, Boolean> {
}
@@ -0,0 +1,109 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.httpauth.basic;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Sa-Token Http Basic 账号
*
* @author click33
* @since 1.41.0
*/
public class SaHttpBasicAccount {
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 构造函数
* @param username 账号
* @param password 密码
*/
public SaHttpBasicAccount(String username, String password) {
this.username = username;
this.password = password;
}
/**
* 构造函数
* @param usernameAndPassword 账号和密码,冒号隔开
*/
public SaHttpBasicAccount(String usernameAndPassword) {
if(SaFoxUtil.isEmpty(usernameAndPassword)) {
throw new SaTokenException("UsernameAndPassword 不能为空");
}
String[] arr = usernameAndPassword.split(":");
if(arr.length != 2) {
throw new SaTokenException("UsernameAndPassword 格式错误,正确格式为:username:password");
}
this.username = arr[0];
this.password = arr[1];
}
/**
* 获取 账号
*
* @return username 账号
*/
public String getUsername() {
return this.username;
}
/**
* 设置 账号
*
* @param username 账号
*/
public void setUsername(String username) {
this.username = username;
}
/**
* 获取 密码
*
* @return password 密码
*/
public String getPassword() {
return this.password;
}
/**
* 设置 密码
*
* @param password 密码
*/
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "SaHttpBasicAccount{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
@@ -45,7 +45,7 @@ public class SaHttpBasicTemplate {
}
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* 获取浏览器提交的 Http Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public String getAuthorizationValue() {
@@ -61,7 +61,19 @@ public class SaHttpBasicTemplate {
// 裁剪前缀并解码
return SaBase64Util.decode(authorization.substring(6));
}
/**
* 获取 Http Basic 账号密码对象
* @return /
*/
public SaHttpBasicAccount getHttpBasicAccount() {
String authorizationValue = getAuthorizationValue();
if(authorizationValue == null) {
return null;
}
return new SaHttpBasicAccount(authorizationValue);
}
/**
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
*/
@@ -32,13 +32,21 @@ public class SaHttpBasicUtil {
public static SaHttpBasicTemplate saHttpBasicTemplate = new SaHttpBasicTemplate();
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* 获取浏览器提交的 Http Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public static String getAuthorizationValue() {
return saHttpBasicTemplate.getAuthorizationValue();
}
/**
* 获取 Http Basic 账号密码对象
* @return /
*/
public static SaHttpBasicAccount getHttpBasicAccount() {
return saHttpBasicTemplate.getHttpBasicAccount();
}
/**
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
*/
@@ -26,18 +26,41 @@ import java.util.Map;
public interface SaJsonTemplate {
/**
* 将任意对象序列化为 json 字符串
* 序列化:对象 -> json 字符串
*
* @param obj 对象
* @return 转换后的 json 字符串
* @param obj /
* @return /
*/
String toJsonString(Object obj);
String objectToJson(Object obj);
/**
* 解析 json 字符串为 map 对象
* @param jsonStr json 字符串
* @return map 对象
* 反序列化:json 字符串 对象
*
* @param jsonStr /
* @param type /
* @return /
* @param <T> /
*/
Map<String, Object> parseJsonToMap(String jsonStr);
<T>T jsonToObject(String jsonStr, Class<T> type);
/**
* 反序列化:json 字符串 → 对象 (自动判断类型)
*
* @param jsonStr /
* @return /
*/
default Object jsonToObject(String jsonStr) {
return jsonToObject(jsonStr, Object.class);
};
/**
* 反序列化:json 字符串 → Map
*
* @param jsonStr /
* @return /
*/
default Map<String, Object> jsonToMap(String jsonStr) {
return jsonToObject(jsonStr, Map.class);
};
}
@@ -15,11 +15,11 @@
*/
package cn.dev33.satoken.json;
import java.util.Map;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotImplException;
import java.util.Map;
/**
* JSON 转换器,默认实现类
*
@@ -33,12 +33,22 @@ public class SaJsonTemplateDefaultImpl implements SaJsonTemplate {
public static final String ERROR_MESSAGE = "未实现具体的 json 转换器";
@Override
public String toJsonString(Object obj) {
public String objectToJson(Object obj) {
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
}
@Override
public Map<String, Object> parseJsonToMap(String jsonStr) {
public Object jsonToObject(String jsonStr) {
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
}
@Override
public <T> T jsonToObject(String jsonStr, Class<T> type) {
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
}
@Override
public Map<String, Object> jsonToMap(String jsonStr) {
throw new NotImplException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10003);
}
@@ -22,7 +22,7 @@ import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
/**
@@ -149,11 +149,11 @@ public class SaTokenEventCenter {
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue 本次登录产生的 token 值
* @param loginModel 登录参数
* @param loginParameter 登录参数
*/
public static void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
public static void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
for (SaTokenListener listener : listenerList) {
listener.doLogin(loginType, loginId, tokenValue, loginModel);
listener.doLogin(loginType, loginId, tokenValue, loginParameter);
}
}
@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
/**
@@ -35,9 +35,9 @@ public interface SaTokenListener {
* @param loginType 账号类别
* @param loginId 账号id
* @param tokenValue 本次登录产生的 token 值
* @param loginModel 登录参数
* @param loginParameter 登录参数
*/
void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel);
void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter);
/**
* 每次注销时触发
@@ -17,7 +17,7 @@ package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -35,7 +35,7 @@ public class SaTokenListenerForLog implements SaTokenListener {
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
log.info("账号 {} 登录成功 (loginType={}), 会话凭证 token={}", loginId, loginType, tokenValue);
}
@@ -15,7 +15,7 @@
*/
package cn.dev33.satoken.listener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
/**
* Sa-Token 侦听器,默认空实现
@@ -28,7 +28,7 @@ import cn.dev33.satoken.stp.SaLoginModel;
public class SaTokenListenerForSimple implements SaTokenListener {
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
}
@@ -15,6 +15,8 @@
*/
package cn.dev33.satoken.model.wrapperInfo;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* 返回值包装类:描述一个账号是否已被封禁等信息
*
@@ -66,7 +68,7 @@ public class SaDisableWrapperInfo {
* @return /
*/
public static SaDisableWrapperInfo createNotDisabled() {
return new SaDisableWrapperInfo(false, 0, 0);
return new SaDisableWrapperInfo(false, 0, SaTokenConsts.NOT_DISABLE_LEVEL);
}
/**
@@ -75,7 +77,7 @@ public class SaDisableWrapperInfo {
* @return /
*/
public static SaDisableWrapperInfo createNotDisabled(long cacheTime) {
return new SaDisableWrapperInfo(false, cacheTime, 0);
return new SaDisableWrapperInfo(false, cacheTime, SaTokenConsts.NOT_DISABLE_LEVEL);
}
@Override
@@ -0,0 +1,38 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.plugin;
/**
* Sa-Token 插件总接口
*
* @author click33
* @since 1.41.0
*/
public interface SaTokenPlugin {
/**
* 安装插件
*/
void install();
/**
* 卸载插件
*/
default void destroy(){
}
}
@@ -0,0 +1,411 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.plugin;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.SaTokenPluginException;
import cn.dev33.satoken.fun.hooks.SaTokenPluginHookFunction;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Sa-Token 插件管理器,管理所有插件的加载与卸载
*
* @author click33
* @since 1.41.0
*/
public class SaTokenPluginHolder {
/**
* 默认实例,非单例模式,可替换
*/
public static SaTokenPluginHolder instance = new SaTokenPluginHolder();
// ------------------- 插件管理器初始化相关 -------------------
/**
* 是否已经加载过插件
*/
public boolean isLoader = false;
/**
* SPI 文件所在目录名称
*/
public String spiDir = "satoken";
/**
* 初始化加载所有插件(多次调用只会执行一次)
*/
public synchronized void init() {
if(isLoader) {
return;
}
loaderPlugins();
isLoader = true;
}
/**
* 根据 SPI 机制加载所有插件
* <p>
* 加载所有 jar 下 /META-INF/satoken/ 目录下 cn.dev33.satoken.plugin.SaTokenPlugin 文件指定的实现类
* </p>
*/
public synchronized void loaderPlugins() {
SaManager.getLog().info("SPI plugin loading start ...");
List<SaTokenPlugin> plugins = _loaderPluginsBySpi(SaTokenPlugin.class, spiDir);
for (SaTokenPlugin plugin : plugins) {
installPlugin(plugin);
}
SaManager.getLog().info("SPI plugin loading end ...");
}
/**
* 自定义 SPI 读取策略 (无状态函数)
* @param serviceInterface SPI 接口
* @param dirName 目录名称
* @return /
* @param <T> /
*/
protected <T> List<T> _loaderPluginsBySpi(Class<T> serviceInterface, String dirName) {
String path = "META-INF/" + dirName + "/" + serviceInterface.getName();
List<T> providers = new ArrayList<>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try (InputStream is = url.openStream()) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
// 忽略空行和注释行
if (!line.isEmpty() && !line.startsWith("#")) {
Class<?> clazz = Class.forName(line, true, classLoader);
T instance = serviceInterface.cast(clazz.getDeclaredConstructor().newInstance());
providers.add(instance);
}
}
} catch (Exception e) {
throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
}
}
} catch (Exception e) {
throw new SaTokenPluginException("SPI 插件加载失败: " + e.getMessage(), e);
}
return providers;
}
// ------------------- 插件管理 -------------------
/**
* 所有插件的集合
*/
private final List<SaTokenPlugin> pluginList = new ArrayList<>();
/**
* 获取插件集合副本 (拷贝插件集合,而非每个插件实例)
* @return /
*/
public synchronized List<SaTokenPlugin> getPluginListCopy() {
return new ArrayList<>(pluginList);
}
/**
* 判断是否已经安装了指定插件
*
* @param pluginClass 插件类型
* @return /
*/
public synchronized<T extends SaTokenPlugin> boolean isInstalledPlugin(Class<T> pluginClass) {
for (SaTokenPlugin plugin : pluginList) {
if (plugin.getClass().equals(pluginClass)) {
return true;
}
}
return false;
}
/**
* 获取指定类型的插件
* @param pluginClass /
* @return /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> T getPlugin(Class<T> pluginClass) {
for (SaTokenPlugin plugin : pluginList) {
if (plugin.getClass().equals(pluginClass)) {
return (T) plugin;
}
}
return null;
}
/**
* 消费指定集合的钩子函数,返回消费的数量
* @param pluginClass /
* @param hooks /
* @param <T> /
*/
protected synchronized <T extends SaTokenPlugin> int _consumeHooks(List<SaTokenPluginHookModel<? extends SaTokenPlugin>> hooks, Class<T> pluginClass) {
int consumeCount = 0;
for (int i = 0; i < hooks.size(); i++) {
SaTokenPluginHookModel<? extends SaTokenPlugin> model = hooks.get(i);
if(model.listenerClass.equals(pluginClass)) {
model.executeFunction.execute(getPlugin(pluginClass));
hooks.remove(i);
i--;
consumeCount++;
}
}
return consumeCount;
}
// ------------------- 插件 Install 与 Destroy -------------------
/**
* 安装指定插件
* @param plugin /
*/
public synchronized SaTokenPluginHolder installPlugin(SaTokenPlugin plugin) {
// 插件为空,拒绝安装
if (plugin == null) {
throw new SaTokenPluginException("插件不可为空");
}
// 插件已经被安装过了,拒绝再次安装
if (isInstalledPlugin(plugin.getClass())) {
throw new SaTokenPluginException("插件 [ " + plugin.getClass().getCanonicalName() + " ] 已安装,不可重复安装");
}
// 执行该插件的 install 前置钩子
_consumeHooks(beforeInstallHooks, plugin.getClass());
// 插件安装
int consumeCount = _consumeHooks(installHooks, plugin.getClass());
if (consumeCount == 0) {
plugin.install();
}
// 执行该插件的 install 后置钩子
_consumeHooks(afterInstallHooks, plugin.getClass());
// 添加到插件集合
pluginList.add(plugin);
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 安装指定插件,根据插件类型
* @param pluginClass /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder installPlugin(Class<T> pluginClass) {
try {
T plugin = pluginClass.getDeclaredConstructor().newInstance();
return installPlugin(plugin);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new SaTokenPluginException(e);
}
}
/**
* 卸载指定插件
* @param plugin /
*/
public synchronized SaTokenPluginHolder destroyPlugin(SaTokenPlugin plugin) {
// 插件为空,拒绝卸载
if (plugin == null) {
throw new SaTokenPluginException("插件不可为空");
}
// 插件未被安装,拒绝卸载
if (!isInstalledPlugin(plugin.getClass())) {
throw new SaTokenPluginException("插件 [ " + plugin.getClass().getCanonicalName() + " ] 未安装,无法卸载");
}
// 执行该插件的 destroy 前置钩子
_consumeHooks(beforeDestroyHooks, plugin.getClass());
// 插件卸载
int consumeCount = _consumeHooks(destroyHooks, plugin.getClass());
if (consumeCount == 0) {
plugin.destroy();
}
// 执行该插件的 destroy 后置钩子
_consumeHooks(afterDestroyHooks, plugin.getClass());
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 卸载指定插件,根据插件类型
* @param pluginClass /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder destroyPlugin(Class<T> pluginClass) {
return destroyPlugin(getPlugin(pluginClass));
}
// ------------------- 插件 Install 钩子 -------------------
/**
* 插件 [ Install 钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> installHooks = new ArrayList<>();
/**
* 插件 [ Install 前置钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> beforeInstallHooks = new ArrayList<>();
/**
* 插件 [ Install 后置钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> afterInstallHooks = new ArrayList<>();
/**
* 注册指定插件的 [ Install 钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则抛出异常。3、注册 Install 钩子的插件默认安装行为将不再执行
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
// 如果指定的插件已经安装完毕,则不再允许注册前置钩子函数
if(isInstalledPlugin(listenerClass)) {
throw new SaTokenPluginException("插件 [ " + listenerClass.getCanonicalName() + " ] 已安装完毕,不允许再注册 Install 钩子函数");
}
// 堆积到钩子函数集合
installHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 注册指定插件的 [ Install 前置钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则抛出异常
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onBeforeInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
// 如果指定的插件已经安装完毕,则不再允许注册前置钩子函数
if(isInstalledPlugin(listenerClass)) {
throw new SaTokenPluginException("插件 [ " + listenerClass.getCanonicalName() + " ] 已安装完毕,不允许再注册 Install 前置钩子函数");
}
// 堆积到钩子函数集合
beforeInstallHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 注册指定插件的 [ Install 后置钩子 ],1、同插件支持多次注册。2、如果插件已经安装完毕,则立即执行该钩子函数
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onAfterInstall(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
// 如果指定的插件已经安装完毕,则立即执行该钩子函数
if(isInstalledPlugin(listenerClass)) {
executeFunction.execute(getPlugin(listenerClass));
return this;
}
// 堆积到钩子函数集合
afterInstallHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
// ------------------- 插件 Destroy 钩子 -------------------
/**
* 插件 [ Destroy 钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> destroyHooks = new ArrayList<>();
/**
* 插件 [ Destroy 前置钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> beforeDestroyHooks = new ArrayList<>();
/**
* 插件 [ Destroy 后置钩子 ] 集合
*/
private final List<SaTokenPluginHookModel<? extends SaTokenPlugin>> afterDestroyHooks = new ArrayList<>();
/**
* 注册指定插件的 [ Destroy 钩子 ],1、同插件支持多次注册。2、注册 Destroy 钩子的插件默认卸载行为将不再执行
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
destroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 注册指定插件的 [ Destroy 前置钩子 ],同插件支持多次注册
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onBeforeDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
beforeDestroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
/**
* 注册指定插件的 [ Destroy 后置钩子 ],同插件支持多次注册
* @param listenerClass /
* @param executeFunction /
* @param <T> /
*/
public synchronized<T extends SaTokenPlugin> SaTokenPluginHolder onAfterDestroy(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
afterDestroyHooks.add(new SaTokenPluginHookModel<T>(listenerClass, executeFunction));
// 返回对象自身,支持连缀风格调用
return this;
}
}
@@ -13,35 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.solon.dao;
package cn.dev33.satoken.plugin;
import cn.dev33.satoken.session.SaSession;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import cn.dev33.satoken.fun.hooks.SaTokenPluginHookFunction;
/**
* Jackson定制版SaSession忽略 timeout 等属性的序列化
*
* Sa-Token 插件 Hook Model
*
* @author click33
* @since 1.34.0
* @since 1.41.0
*/
@JsonIgnoreProperties({"timeout"})
public class SaSessionForJacksonCustomized extends SaSession {
public class SaTokenPluginHookModel<T extends SaTokenPlugin> {
/**
*
* 监听插件类型
*/
private static final long serialVersionUID = -7600983549653130681L;
public SaSessionForJacksonCustomized() {
super();
}
public Class<T> listenerClass;
/**
* 构建一个Session对象
* @param id Session的id
* 执行的方法
*/
public SaSessionForJacksonCustomized(String id) {
super(id);
public SaTokenPluginHookFunction<T> executeFunction;
/**
* 构造函数
* @param listenerClass /
* @param executeFunction /
*/
public SaTokenPluginHookModel(Class<T> listenerClass, SaTokenPluginHookFunction<T> executeFunction) {
this.listenerClass = listenerClass;
this.executeFunction = executeFunction;
}
}
@@ -15,15 +15,15 @@
*/
package cn.dev33.satoken.router;
import java.util.List;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.fun.SaParamRetFunction;
import cn.dev33.satoken.strategy.SaStrategy;
import java.util.List;
/**
* 路由匹配操作工具类
@@ -55,7 +55,7 @@ public class SaRouter {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
return SaStrategy.instance.routeMatcher.apply(pattern, path);
}
/**
@@ -46,6 +46,7 @@ import java.security.SecureRandom;
* @author Damien Miller
* @since 1.29.0
*/
@Deprecated
@SuppressWarnings("all")
public class BCrypt {
// BCrypt parameters
@@ -0,0 +1,126 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.secure;
import java.nio.charset.StandardCharsets;
/**
* Sa-Token Base32 工具类
*
* @author click33
* @since 1.42.0
*/
public class SaBase32Util {
private static final String BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static final int[] BASE32_LOOKUP = new int[256];
static {
// 初始化解码查找表
for (int i = 0; i < BASE32_CHARS.length(); i++) {
char c = BASE32_CHARS.charAt(i);
BASE32_LOOKUP[c] = i;
// 支持小写字母解码
if (c >= 'A' && c <= 'Z') {
BASE32_LOOKUP[Character.toLowerCase(c)] = i;
}
}
}
/**
* Base32 编码(byte[] 转 String
*/
public static String encodeBytesToString(byte[] bytes) {
if (bytes == null) return null;
StringBuilder result = new StringBuilder();
int buffer = 0;
int bufferSize = 0;
for (byte b : bytes) {
buffer = (buffer << 8) | (b & 0xFF);
bufferSize += 8;
while (bufferSize >= 5) {
bufferSize -= 5;
int index = (buffer >> bufferSize) & 0x1F;
result.append(BASE32_CHARS.charAt(index));
}
}
// 处理剩余位
if (bufferSize > 0) {
int index = (buffer << (5 - bufferSize)) & 0x1F;
result.append(BASE32_CHARS.charAt(index));
}
return result.toString();
}
/**
* Base32 解码(String 转 byte[]
*/
public static byte[] decodeStringToBytes(String text) {
if (text == null) return null;
text = text.replaceAll("=", "").trim();
if (text.isEmpty()) return new byte[0];
int buffer = 0;
int bufferSize = 0;
int byteCount = (text.length() * 5 + 7) / 8;
byte[] bytes = new byte[byteCount];
int byteIndex = 0;
for (char c : text.toCharArray()) {
int value = BASE32_LOOKUP[c];
if (value == 0 && c != 'A') continue; // 跳过非法字符
buffer = (buffer << 5) | value;
bufferSize += 5;
if (bufferSize >= 8) {
bufferSize -= 8;
bytes[byteIndex++] = (byte) ((buffer >> bufferSize) & 0xFF);
}
}
// 处理最后一个字节
if (bufferSize > 0) {
bytes[byteIndex] = (byte) ((buffer << (8 - bufferSize)) & 0xFF);
}
return bytes;
}
/**
* Base32 编码(String 转 String
*/
public static String encode(String text) {
if (text == null) return null;
return encodeBytesToString(text.getBytes(StandardCharsets.UTF_8));
}
/**
* Base32 解码(String 转 String
*/
public static String decode(String base32Text) {
if (base32Text == null) return null;
byte[] bytes = decodeStringToBytes(base32Text);
return new String(bytes, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,181 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.secure.totp;
import cn.dev33.satoken.exception.TotpAuthException;
import cn.dev33.satoken.secure.SaBase32Util;
import cn.dev33.satoken.util.StrFormatter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.time.Instant;
/**
* TOTP 算法类,支持 生成/验证 动态一次性密码
*
* @author click33
* @since 1.42.0
*/
public class SaTotpTemplate {
/**
* 时间窗口步长(秒)
*/
public int timeStep = 30;
/**
* 生成的验证码位数
*/
public int codeDigits = 6;
/**
* 哈希算法(HmacSHA1、HmacSHA256等)
*/
public String hmacAlgorithm = "HmacSHA1";
/**
* 密钥长度(字节,推荐16或32)
*/
public int secretKeyLength = 16;
/**
* 构造函数 (使用默认参数)
*/
public SaTotpTemplate() {
}
/**
* 构造函数 (使用自定义参数)
*
* @param timeStep 时间窗口步长(秒)
* @param codeDigits 生成的验证码位数
* @param hmacAlgorithm 哈希算法(HmacSHA1、HmacSHA256等)
* @param secretKeyLength 密钥长度(字节,推荐16或32)
*/
public SaTotpTemplate(int timeStep, int codeDigits, String hmacAlgorithm, int secretKeyLength) {
this.timeStep = timeStep;
this.codeDigits = codeDigits;
this.hmacAlgorithm = hmacAlgorithm;
this.secretKeyLength = secretKeyLength;
}
/**
* 生成随机密钥(Base32编码)
*
* @return /
*/
public String generateSecretKey() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[secretKeyLength];
random.nextBytes(bytes);
return SaBase32Util.encodeBytesToString(bytes).replace("=", "");
}
/**
* 生成当前时间的 TOTP 验证码
*
* @param secretKey Base32 编码的密钥
* @return /
*/
public String _generateTOTP(String secretKey) {
return _generateTOTP(secretKey, Instant.now().getEpochSecond());
}
/**
* 判断用户输入的 TOTP 是否有效
*
* @param secretKey Base32编码的密钥
* @param code 用户输入的验证码
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
* @return /
*/
public boolean validateTOTP(String secretKey, String code, int timeWindowOffset) {
long currentWindow = Instant.now().getEpochSecond() / timeStep;
for (int i = -timeWindowOffset; i <= timeWindowOffset; i++) {
String calculatedCode = _generateTOTP(secretKey, (currentWindow + i) * timeStep);
if (calculatedCode.equals(code)) {
return true;
}
}
return false;
}
/**
* 校验用户输入的TOTP是否有效,如果无效则抛出异常
*
* @param secretKey Base32编码的密钥
* @param code 用户输入的验证码
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
*/
public void checkTOTP(String secretKey, String code, int timeWindowOffset) {
if (!validateTOTP(secretKey, code, timeWindowOffset)) {
throw new TotpAuthException();
}
}
/**
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
*
* @param account 账户名
* @return /
*/
public String generateGoogleSecretKey(String account) {
return generateGoogleSecretKey(account, generateSecretKey());
}
/**
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
*
* @param account 账户名
* @param secretKey TOTP 秘钥
* @return /
*/
public String generateGoogleSecretKey(String account, String secretKey) {
return StrFormatter.format("otpauth://totp/{}?secret={}", account, secretKey);
}
protected String _generateTOTP(String secretKey, long time) {
// Base32解码密钥
byte[] keyBytes = SaBase32Util.decodeStringToBytes(secretKey);
byte[] counterBytes = ByteBuffer.allocate(8).putLong(time / timeStep).array();
try {
// 计算HMAC哈希
Mac hmac = Mac.getInstance(hmacAlgorithm);
hmac.init(new SecretKeySpec(keyBytes, hmacAlgorithm));
byte[] hash = hmac.doFinal(counterBytes);
// 动态截断(RFC 6238
int offset = hash[hash.length - 1] & 0xF;
int binary = ((hash[offset] & 0x7F) << 24)
| ((hash[offset + 1] & 0xFF) << 16)
| ((hash[offset + 2] & 0xFF) << 8)
| (hash[offset + 3] & 0xFF);
// 生成指定位数的验证码
int otp = binary % (int) Math.pow(10, codeDigits);
return String.format("%0" + codeDigits + "d", otp);
} catch (GeneralSecurityException e) {
throw new RuntimeException("TOTP生成失败", e);
}
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.secure.totp;
import cn.dev33.satoken.SaManager;
/**
* TOTP 工具类,支持 生成/验证 动态一次性密码
*
* @author click33
* @since 1.42.0
*/
public class SaTotpUtil {
/**
* 生成随机密钥(Base32编码)
*
* @return /
*/
public static String generateSecretKey() {
return SaManager.getSaTotpTemplate().generateSecretKey();
}
/**
* 生成当前时间的TOTP验证码
*
* @param secretKey Base32编码的密钥
* @return /
*/
public static String generateTOTP(String secretKey) {
return SaManager.getSaTotpTemplate()._generateTOTP(secretKey);
}
/**
* 判断用户输入的TOTP是否有效
*
* @param secretKey Base32编码的密钥
* @param code 用户输入的验证码
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
* @return /
*/
public static boolean validateTOTP(String secretKey, String code, int timeWindowOffset) {
return SaManager.getSaTotpTemplate().validateTOTP(secretKey, code, timeWindowOffset);
}
/**
* 校验用户输入的TOTP是否有效,如果无效则抛出异常
*
* @param secretKey Base32编码的密钥
* @param code 用户输入的验证码
* @param timeWindowOffset 允许的时间窗口偏移量(如1表示允许前后各1个时间窗口)
*/
public static void checkTOTP(String secretKey, String code, int timeWindowOffset) {
SaManager.getSaTotpTemplate().checkTOTP(secretKey, code, timeWindowOffset);
}
/**
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
*
* @param account 账户名
* @return /
*/
public static String generateGoogleSecretKey(String account) {
return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account);
}
/**
* 生成谷歌认证器的扫码字符串 (形如:otpauth://totp/{account}?secret={secretKey})
*
* @param account 账户名
* @param secretKey TOTP 秘钥
* @return /
*/
public static String generateGoogleSecretKey(String account, String secretKey) {
return SaManager.getSaTotpTemplate().generateGoogleSecretKey(account, secretKey);
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer;
/**
* 序列化器
*
* @author click33
* @since 1.41.0
*/
public interface SaSerializerTemplate {
/**
* 序列化:对象 -> 字符串
*
* @param obj /
* @return /
*/
String objectToString(Object obj);
/**
* 反序列化:字符串 → 对象
*
* @param str /
* @return /
*/
Object stringToObject(String str);
/**
* 反序列化:字符串 → 对象 (指定类型)
* <p>
* 此方法目前仅为 json 序列化实现类 在 反序列化对象 传递类型信息
* </p>
*
* @param str /
* @return /
*/
@SuppressWarnings("unchecked")
default <T> T stringToObject(String str, Class<T> type) {
return (T)stringToObject(str);
};
/**
* 序列化:对象 -> 字节数组
*
* @param obj /
* @return /
*/
byte[] objectToBytes(Object obj);
/**
* 反序列化:字节数组 → 对象
*
* @param bytes /
* @return /
*/
Object bytesToObject(byte[] bytes);
}
@@ -0,0 +1,94 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer.impl;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.serializer.SaSerializerTemplate;
import java.io.*;
/**
* 序列化器次级实现: jdk序列化
*
* @author click33
* @since 1.41.0
*/
public interface SaSerializerTemplateForJdk extends SaSerializerTemplate {
@Override
default String objectToString(Object obj) {
byte[] bytes = objectToBytes(obj);
if (bytes == null) {
return null;
}
return bytesToString(bytes);
}
@Override
default Object stringToObject(String str) {
if(str == null) {
return null;
}
byte[] bytes = stringToBytes(str);
return bytesToObject(bytes);
}
@Override
default byte[] objectToBytes(Object obj) {
if (obj == null) {
return null;
}
try (
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)
) {
oos.writeObject(obj);
return baos.toByteArray();
} catch (IOException e) {
throw new SaTokenException(e);
}
}
@Override
default Object bytesToObject(byte[] bytes) {
if(bytes == null) {
return null;
}
try (
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)
) {
return ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new SaTokenException(e);
}
}
/**
* byte[] 转换为 String
* @param bytes /
* @return /
*/
String bytesToString(byte[] bytes);
/**
* String 转换为 byte[]
* @param str /
* @return /
*/
byte[] stringToBytes(String str);
}
@@ -13,22 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.dubbo;
package cn.dev33.satoken.serializer.impl;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
import java.util.Base64;
/**
* Sa-Token 二级上下文 - 创建器 [ Dubbo版 ]
*
* 序列化器: jdk序列化Base64 编码 (体积+33%)
*
* @author click33
* @since 1.34.0
* @since 1.41.0
*/
public class SaTokenSecondContextCreatorForDubbo implements SaTokenSecondContextCreator {
public class SaSerializerTemplateForJdkUseBase64 implements SaSerializerTemplateForJdk {
@Override
public SaTokenSecondContext create() {
return new SaTokenSecondContextForDubbo();
public String bytesToString(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
@Override
public byte[] stringToBytes(String str) {
return Base64.getDecoder().decode(str);
}
}
@@ -13,23 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.context.dubbo3;
package cn.dev33.satoken.serializer.impl;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
import cn.dev33.satoken.util.SaHexUtil;
/**
* Sa-Token 二级上下文 - 创建器 [Dubbo3版]
*
* 序列化器: jdk序列化16 进制编码 (体积+100%)
*
* @author click33
* @since 1.34.0
* @since 1.41.0
*/
public class SaTokenSecondContextCreatorForDubbo3 implements SaTokenSecondContextCreator {
public class SaSerializerTemplateForJdkUseHex implements SaSerializerTemplateForJdk {
@Override
public SaTokenSecondContext create() {
return new SaTokenSecondContextForDubbo3();
public String bytesToString(byte[] bytes) {
return SaHexUtil.bytesToHex(bytes);
}
@Override
public byte[] stringToBytes(String str) {
return SaHexUtil.hexToBytes(str);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer.impl;
import java.nio.charset.StandardCharsets;
/**
* 序列化器: jdk序列化、ISO-8859-1 编码 (体积无变化)
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerTemplateForJdkUseISO_8859_1 implements SaSerializerTemplateForJdk {
@Override
public String bytesToString(byte[] bytes) {
return new String(bytes, StandardCharsets.ISO_8859_1);
}
@Override
public byte[] stringToBytes(String str) {
return str.getBytes(StandardCharsets.ISO_8859_1);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.serializer.impl;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.serializer.SaSerializerTemplate;
/**
* 序列化器: 使用 json 转换器
*
* @author click33
* @since 1.41.0
*/
public class SaSerializerTemplateForJson implements SaSerializerTemplate {
@Override
public String objectToString(Object obj) {
return SaManager.getSaJsonTemplate().objectToJson(obj);
}
@Override
public Object stringToObject(String str) {
return SaManager.getSaJsonTemplate().jsonToObject(str);
}
@Override
public <T>T stringToObject(String str, Class<T> type) {
return SaManager.getSaJsonTemplate().jsonToObject(str, type);
}
@Override
public byte[] objectToBytes(Object obj) {
throw new ApiDisabledException("json 序列化器不支持 Object -> byte[]");
}
@Override
public Object bytesToObject(byte[] bytes) {
throw new ApiDisabledException("json 序列化器不支持 byte[] -> Object");
}
}
@@ -18,6 +18,7 @@ package cn.dev33.satoken.session;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.application.SaSetValueInterface;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.fun.SaTwoParamFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -88,6 +89,11 @@ public class SaSession implements SaSetValueInterface, Serializable {
*/
private String token;
/**
* 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*/
private int historyTerminalCount;
/**
* 此 SaSession 的创建时间(13位时间戳)
*/
@@ -234,137 +240,167 @@ public class SaSession implements SaSetValueInterface, Serializable {
}
// ----------------------- TokenSign 相关
// ----------------------- SaTerminalInfo 相关
/**
* 此 Session 绑定的 Token 签名列表
* 登录终端信息列表
*/
private List<TokenSign> tokenSignList = new Vector<>();
private List<SaTerminalInfo> terminalList = new Vector<>();
/**
* 写入此 Session 绑定的 Token 签名列表
* @param tokenSignList Token 签名列表
* 写入登录终端信息列表
* @param terminalList /
*/
public void setTokenSignList(List<TokenSign> tokenSignList) {
this.tokenSignList = tokenSignList;
public void setTerminalList(List<SaTerminalInfo> terminalList) {
this.terminalList = terminalList;
}
/**
* 获取此 Session 绑定的 Token 签名列表
* 获取登录终端信息列表
*
* @return Token 签名列表
* @return /
*/
public List<TokenSign> getTokenSignList() {
return tokenSignList;
public List<SaTerminalInfo> getTerminalList() {
return terminalList;
}
/**
* 获取 Token 签名列表 拷贝副本
* 获取 登录终端信息列表 (拷贝副本)
*
* @return token签名列表
* @return /
*/
public List<TokenSign> tokenSignListCopy() {
return new ArrayList<>(tokenSignList);
public List<SaTerminalInfo> terminalListCopy() {
return new ArrayList<>(terminalList);
}
/**
* 返回 Token 签名列表 拷贝副本,根据 device 筛选
* 获取 登录终端信息列表 (拷贝副本),根据 deviceType 筛选
*
* @param device 设备类型,填 null 代表不限设备类型
* @return token签名列表
* @param deviceType 设备类型,填 null 代表不限设备类型
* @return /
*/
public List<TokenSign> getTokenSignListByDevice(String device) {
public List<SaTerminalInfo> getTerminalListByDeviceType(String deviceType) {
// 返回全部
if(device == null) {
return tokenSignListCopy();
if(deviceType == null) {
return terminalListCopy();
}
// 返回筛选后的
List<TokenSign> tokenSignList = tokenSignListCopy();
List<TokenSign> list = new ArrayList<>();
for (TokenSign tokenSign : tokenSignList) {
if(SaFoxUtil.equals(tokenSign.getDevice(), device)) {
list.add(tokenSign);
List<SaTerminalInfo> copyList = terminalListCopy();
List<SaTerminalInfo> newList = new ArrayList<>();
for (SaTerminalInfo terminal : copyList) {
if(SaFoxUtil.equals(terminal.getDeviceType(), deviceType)) {
newList.add(terminal);
}
}
return list;
return newList;
}
/**
* 获取当前 Session 上的所有 token 列表
* 获取 登录终端 token 列表
*
* @param device 设备类型,填 null 代表不限设备类型
* @param deviceType 设备类型,填 null 代表不限设备类型
* @return 此 loginId 的所有登录 token
*/
public List<String> getTokenValueListByDevice(String device) {
// 遍历解析,按照设备类型进行筛选
List<TokenSign> tokenSignList = tokenSignListCopy();
public List<String> getTokenValueListByDeviceType(String deviceType) {
List<String> tokenValueList = new ArrayList<>();
for (TokenSign tokenSign : tokenSignList) {
if(device == null || tokenSign.getDevice().equals(device)) {
tokenValueList.add(tokenSign.getValue());
}
for (SaTerminalInfo terminal : getTerminalListByDeviceType(deviceType)) {
tokenValueList.add(terminal.getTokenValue());
}
return tokenValueList;
}
/**
* 查找一个 Token 签名
* 查找一个终端信息,根据 tokenValue
*
* @param tokenValue token值
* @return 查找到的 TokenSign
* @param tokenValue /
* @return /
*/
public TokenSign getTokenSign(String tokenValue) {
for (TokenSign tokenSign : tokenSignListCopy()) {
if (SaFoxUtil.equals(tokenSign.getValue(), tokenValue)) {
return tokenSign;
public SaTerminalInfo getTerminal(String tokenValue) {
for (SaTerminalInfo terminal : terminalListCopy()) {
if (SaFoxUtil.equals(terminal.getTokenValue(), tokenValue)) {
return terminal;
}
}
return null;
}
/**
* 添加一个 Token 签名
* 添加一个终端信息
*
* @param tokenSign Token 签名
* @param terminalInfo /
*/
public void addTokenSign(TokenSign tokenSign) {
// 根据 tokenValue 值查重,如果存在,则添加
TokenSign oldTokenSign = getTokenSign(tokenSign.getValue());
if(oldTokenSign == null) {
tokenSignList.add(tokenSign);
update();
} else {
// 如果存在,则更新
oldTokenSign.setValue(tokenSign.getValue());
oldTokenSign.setDevice(tokenSign.getDevice());
oldTokenSign.setTag(tokenSign.getTag());
update();
public void addTerminal(SaTerminalInfo terminalInfo) {
// 根据 tokenValue 值查重,如果存在旧的,则先删除
SaTerminalInfo oldTerminal = getTerminal(terminalInfo.getTokenValue());
if(oldTerminal != null) {
terminalList.remove(oldTerminal);
}
// 然后添加新的
this.historyTerminalCount++;
terminalInfo.setIndex(this.historyTerminalCount);
terminalList.add(terminalInfo);
update();
}
/**
* 添加一个 Token 签名
*
* @param tokenValue token值
* @param device 设备类型
*/
@Deprecated
public void addTokenSign(String tokenValue, String device) {
addTokenSign(new TokenSign(tokenValue, device, null));
}
/**
* 移除一个 Token 签名
* 移除一个终端信息
*
* @param tokenValue token值
*/
public void removeTokenSign(String tokenValue) {
TokenSign tokenSign = getTokenSign(tokenValue);
if (tokenSignList.remove(tokenSign)) {
public void removeTerminal(String tokenValue) {
SaTerminalInfo terminalInfo = getTerminal(tokenValue);
if (terminalList.remove(terminalInfo)) {
update();
}
}
/**
* 获取 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*
* @return /
*/
public int getHistoryTerminalCount() {
return this.historyTerminalCount;
}
/**
* 设置 当前账号历史总计登录设备数量 (当此 SaSession 属于 Account-Session 时,此值有效)
*
* @param historyTerminalCount /
*/
public void setHistoryTerminalCount(int historyTerminalCount) {
this.historyTerminalCount = historyTerminalCount;
}
/**
* 遍历 terminalList 列表,执行特定函数
*
* @param function 需要执行的函数
*/
public void forEachTerminalList(SaTwoParamFunction<SaSession, SaTerminalInfo> function) {
for (SaTerminalInfo terminalInfo: terminalListCopy()) {
function.run(this, terminalInfo);
}
}
/**
* 判断指定设备 id 是否为可信任设备
* @param deviceId /
* @return /
*/
public boolean isTrustDeviceId(String deviceId) {
if(SaFoxUtil.isEmpty(deviceId)) {
return false;
}
for (SaTerminalInfo terminal : terminalListCopy()) {
if (SaFoxUtil.equals(terminal.getDeviceId(), deviceId)) {
return true;
}
}
return false;
}
// ----------------------- 一些操作
@@ -382,9 +418,9 @@ public class SaSession implements SaSetValueInterface, Serializable {
SaTokenEventCenter.doLogoutSession(id);
}
/** 当Session上的tokenSign数量为零时,注销会话 */
public void logoutByTokenSignCountToZero() {
if (tokenSignList.size() == 0) {
/** 当 Session 上的 SaTerminalInfo 数量为零时,注销会话 */
public void logoutByTerminalCountToZero() {
if (terminalList.isEmpty()) {
logout();
}
}
@@ -393,7 +429,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
* 获取此Session的剩余存活时间 (单位: 秒)
* @return 此Session的剩余存活时间 (单位: 秒)
*/
public long getTimeout() {
public long timeout() {
return SaManager.getSaTokenDao().getSessionTimeout(this.id);
}
@@ -411,7 +447,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
*/
public void updateMinTimeout(long minTimeout) {
long min = trans(minTimeout);
long curr = trans(getTimeout());
long curr = trans(timeout());
if(curr < min) {
updateTimeout(minTimeout);
}
@@ -423,7 +459,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
*/
public void updateMaxTimeout(long maxTimeout) {
long max = trans(maxTimeout);
long curr = trans(getTimeout());
long curr = trans(timeout());
if(curr > max) {
updateTimeout(maxTimeout);
}
@@ -546,16 +582,4 @@ public class SaSession implements SaSetValueInterface, Serializable {
//
/**
* 请更换为:getTokenSignListByDevice(device)
*
* @param device 设备类型,填 null 代表不限设备类型
* @return token签名列表
*/
@Deprecated
public List<TokenSign> tokenSignListCopyByDevice(String device) {
return getTokenSignListByDevice(device);
}
}
@@ -0,0 +1,265 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.session;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 登录设备终端信息 Model
*
* @author click33
* @since 1.41.0
*/
public class SaTerminalInfo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1406115065849845073L;
/**
* 登录会话索引值 (该账号第几个登录的设备, 从 1 开始)
*/
private int index;
/**
* Token 值
*/
private String tokenValue;
/**
* 所属设备类型,例如:PC、WEB、HD、MOBILE、APP
*/
private String deviceType;
/**
* 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
*/
private String deviceId;
/**
* 此次登录的自定义扩展数据 (只允许在登录前设定,登录后不建议更改)
*/
private Map<String, Object> extraData;
/**
* 创建时间
*/
private long createTime;
/**
* 构建一个
*/
public SaTerminalInfo() {
}
/**
* 构建一个
*
* @param index 登录会话索引值 (该账号第几个登录的设备)
* @param tokenValue Token 值
* @param deviceType 所属设备类型
* @param extraData 此客户端登录的挂载数据
*/
public SaTerminalInfo(int index, String tokenValue, String deviceType, Map<String, Object> extraData) {
this.index = index;
this.tokenValue = tokenValue;
this.deviceType = deviceType;
this.extraData = extraData;
this.createTime = System.currentTimeMillis();
}
// 扩展方法
/**
* 此次登录的自定义扩展数据 (只允许在登录前设定,登录后不建议更改)
* @param key 键
* @param value 值
* @return 对象自身
*/
public SaTerminalInfo setExtra(String key, Object value) {
if(this.extraData == null) {
this.extraData = new LinkedHashMap<>();
}
this.extraData.put(key, value);
return this;
}
/**
* 此次登录的自定义扩展数据
* @param key 键
* @return 扩展数据的值
*/
public Object getExtra(String key) {
if(this.extraData == null) {
return null;
}
return this.extraData.get(key);
}
/**
* 判断是否设置了扩展数据
* @return /
*/
public boolean haveExtraData() {
return extraData != null && !extraData.isEmpty();
}
// -------------------- get/set --------------------
/**
* 获取 登录会话索引值 (该账号第几个登录的设备)
*
* @return index 登录会话索引值 (该账号第几个登录的设备)
*/
public int getIndex() {
return this.index;
}
/**
* 设置 登录会话索引值 (该账号第几个登录的设备)
*
* @param index 登录会话索引值 (该账号第几个登录的设备)
* @return 对象自身
*/
public SaTerminalInfo setIndex(int index) {
this.index = index;
return this;
}
/**
* @return Token 值
*/
public String getTokenValue() {
return tokenValue;
}
/**
* 写入 Token 值
*
* @param tokenValue /
* @return 对象自身
*/
public SaTerminalInfo setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
return this;
}
/**
* @return 所属设备类型
*/
public String getDeviceType() {
return deviceType;
}
/**
* 写入所属设备类型
*
* @param deviceType /
* @return 对象自身
*/
public SaTerminalInfo setDeviceType(String deviceType) {
this.deviceType = deviceType;
return this;
}
/**
* 获取 登录设备唯一标识
*
* @return deviceId 登录设备唯一标识
*/
public String getDeviceId() {
return this.deviceId;
}
/**
* 设置 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
*
* @param deviceId 登录设备唯一标识,例如:kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidM
* @return 对象自身
*/
public SaTerminalInfo setDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
/**
* 获取 此客户端登录的挂载数据
*
* @return /
*/
public Map<String, Object> getExtraData() {
return this.extraData;
}
/**
* 设置 此客户端登录的挂载数据
*
* @param extraData /
* @return 对象自身
*/
public SaTerminalInfo setExtraData(Map<String, Object> extraData) {
this.extraData = extraData;
return this;
}
/**
* 获取 创建时间
*
* @return createTime 创建时间
*/
public long getCreateTime() {
return this.createTime;
}
/**
* 设置 创建时间
*
* @param createTime 创建时间
* @return 对象自身
*/
public SaTerminalInfo setCreateTime(long createTime) {
this.createTime = createTime;
return this;
}
//
@Override
public String toString() {
return "SaTerminalInfo [" +
"index=" + index +
", tokenValue='" + tokenValue +
", deviceType='" + deviceType +
", deviceId='" + deviceId +
", extraData=" + extraData +
", createTime=" + createTime +
']';
}
/*
* Expand in the future:
* deviceName 登录设备端名称,一般为浏览器名称
* systemName 登录设备操作系统名称
* loginIp 登录IP地址
* address 登录设备地理位置
* loginTime 登录时间
*/
}
@@ -1,131 +0,0 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.session;
import java.io.Serializable;
/**
* Token 签名 Model
*
* <p> 挂在到 SaSession 上的 Token 签名,一般情况下,一个 TokenSign 代表一个登录的会话。</p>
*
* @author click33
* @since 1.8.0
*/
public class TokenSign implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1406115065849845073L;
/**
* Token 值
*/
private String value;
/**
* 所属设备类型
*/
private String device;
/**
* 此客户端登录的挂载数据
*/
private Object tag;
/**
* 构建一个
*/
public TokenSign() {
}
/**
* 构建一个
*
* @param value Token 值
* @param device 所属设备类型
* @param tag 此客户端登录的挂载数据
*/
public TokenSign(String value, String device, Object tag) {
this.value = value;
this.device = device;
this.tag = tag;
}
/**
* @return Token 值
*/
public String getValue() {
return value;
}
/**
* @return 所属设备类型
*/
public String getDevice() {
return device;
}
/**
* 写入 Token 值
*
* @param value /
* @return 对象自身
*/
public TokenSign setValue(String value) {
this.value = value;
return this;
}
/**
* 写入所属设备类型
*
* @param device /
* @return 对象自身
*/
public TokenSign setDevice(String device) {
this.device = device;
return this;
}
/**
* 获取 此客户端登录的挂载数据
*
* @return /
*/
public Object getTag() {
return this.tag;
}
/**
* 设置 此客户端登录的挂载数据
*
* @param tag /
* @return 对象自身
*/
public TokenSign setTag(Object tag) {
this.tag = tag;
return this;
}
//
@Override
public String toString() {
return "TokenSign [value=" + value + ", device=" + device + ", tag=" + tag + "]";
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.session.raw;
import cn.dev33.satoken.session.SaSession;
/**
* SaSession 读写工具类 委托
*
* @author click33
* @since 1.42.0
*/
public class SaRawSessionDelegator {
/**
* raw session 类型
*/
public String type;
public SaRawSessionDelegator(String type) {
this.type = type;
}
/**
* 判断:指定 SaSession 是否存在
*
* @param valueId /
* @return 是否存在
*/
public boolean isExists(Object valueId) {
return SaRawSessionUtil.isExists(type, valueId);
}
/**
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建,isCreate 参数代表是否则新建并返回
*
* @param valueId /
* @param isCreate 如果此 SaSession 尚未在 DB 创建,是否新建并返回
* @return SaSession 对象
*/
public SaSession getSessionById(Object valueId, boolean isCreate) {
return SaRawSessionUtil.getSessionById(type, valueId, isCreate);
}
/**
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建,则新建并返回
*
* @param valueId /
* @return SaSession 对象
*/
public SaSession getSessionById(Object valueId) {
return SaRawSessionUtil.getSessionById(type, valueId);
}
/**
* 删除指定 SaSession
*
* @param valueId /
*/
public void deleteSessionById(Object valueId) {
SaRawSessionUtil.deleteSessionById(type, valueId);
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.session.raw;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
/**
* SaSession 读写工具类
*
* @author click33
* @since 1.42.0
*/
public class SaRawSessionUtil {
private SaRawSessionUtil() {
}
/**
* 拼接Key: 在存储 SaSession 时应该使用的 key
*
* @param type 类型
* @param valueId 唯一标识
* @return sessionId
*/
public static String splicingSessionKey(String type, Object valueId) {
return SaManager.getConfig().getTokenName() + ":raw-session:" + type + ":" + valueId;
}
/**
* 判断:指定 SaSession 是否存在
*
* @param type /
* @param valueId /
* @return 是否存在
*/
public static boolean isExists(String type, Object valueId) {
return SaManager.getSaTokenDao().getSession(splicingSessionKey(type, valueId)) != null;
}
/**
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建,isCreate 参数代表是否则新建并返回
*
* @param type /
* @param valueId /
* @param isCreate 如果此 SaSession 尚未在 DB 创建,是否新建并返回
* @return SaSession 对象
*/
public static SaSession getSessionById(String type, Object valueId, boolean isCreate) {
String sessionId = splicingSessionKey(type, valueId);
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
if (session == null && isCreate) {
session = SaStrategy.instance.createSession.apply(sessionId);
session.setType(type);
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
}
return session;
}
/**
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建,则新建并返回
*
* @param type /
* @param valueId /
* @return SaSession 对象
*/
public static SaSession getSessionById(String type, Object valueId) {
return getSessionById(type, valueId, true);
}
/**
* 删除指定 SaSession
*
* @param type /
* @param valueId /
*/
public static void deleteSessionById(String type, Object valueId) {
SaManager.getSaTokenDao().deleteSession(splicingSessionKey(type, valueId));
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sign;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSignConfig;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaSignException;
import cn.dev33.satoken.fun.SaParamRetFunction;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* API 参数签名算法 - 多实例总控类
*
* @author click33
* @since 1.41.0
*/
public class SaSignMany {
/**
* 根据 appid 获取 SaSignConfig,允许自定义
*/
public static SaParamRetFunction<String, SaSignConfig> findSaSignConfigMethod = (appid) -> {
return SaManager.getConfig().getSignMany().get(appid);
};
/**
* 获取 SaSignTemplate,根据 appid
* @param appid /
* @return /
*/
public static SaSignTemplate getSignTemplate(String appid) {
// appid 为空,返回全局默认 SaSignTemplate
if(SaFoxUtil.isEmpty(appid)){
return SaManager.getSaSignTemplate();
}
// 获取 SaSignConfig
SaSignConfig config = findSaSignConfigMethod.run(appid);
if(config == null){
throw new SaSignException("未找到签名配置,appid=" + appid).setCode(SaErrorCode.CODE_12211);
}
// 创建 SaSignTemplate 并返回
return new SaSignTemplate(config);
}
}
@@ -20,7 +20,6 @@ import cn.dev33.satoken.config.SaSignConfig;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaSignException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.Map;
@@ -173,7 +172,7 @@ public class SaSignTemplate {
// 计算签名
String paramsStr = joinParamsDictSort(paramsMap);
String fullStr = paramsStr + "&" + key + "=" + secretKey;
String signStr = abstractStr(fullStr);
String signStr = digestFullStr(fullStr);
// 输入日志,方便调试
log.debug("fullStr{}", fullStr);
@@ -188,8 +187,8 @@ public class SaSignTemplate {
* @param fullStr 待摘要的字符串
* @return 签名
*/
public String abstractStr(String fullStr) {
return SaSecureUtil.md5(fullStr);
public String digestFullStr(String fullStr) {
return getSignConfigOrGlobal().digestMethod.run(fullStr);
}
/**
@@ -281,7 +280,7 @@ public class SaSignTemplate {
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* 判断:给定的参数 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
@@ -292,7 +291,7 @@ public class SaSignTemplate {
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* 校验:给定的参数 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param sign 待验证的签名
*/
@@ -349,6 +348,9 @@ public class SaSignTemplate {
// 通过 √
}
// ----------- Web 请求相关 封装
/**
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
* @param request 待校验的请求对象
@@ -365,8 +367,8 @@ public class SaSignTemplate {
/**
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
* @param request 待校验的请求对象
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
*/
public void checkRequest(SaRequest request, String... paramNames) {
if (paramNames.length == 0) {
@@ -382,7 +384,7 @@ public class SaSignTemplate {
* @param paramNames 指定的参数名称,不可为空,如果传入空数组则代表只拿 timestamp、nonce、sign 三个参数
* @return 提取出的参数
*/
public Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
protected Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
Map<String, String> paramMap = new TreeMap<>();
// 此三个参数是必须获取的
@@ -119,7 +119,7 @@ public class SaSignUtil {
}
/**
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
* 判断:给定的参数 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
@@ -129,7 +129,7 @@ public class SaSignUtil {
}
/**
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
* 校验:给定的参数 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param sign 待验证的签名
*/
@@ -154,21 +154,26 @@ public class SaSignUtil {
SaManager.getSaSignTemplate().checkParamMap(paramMap);
}
// ----------- Web 请求相关 封装
/**
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
* @param request 待校验的请求对象
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
* @return 是否合法
*/
public static boolean isValidRequest(SaRequest request) {
return SaManager.getSaSignTemplate().isValidRequest(request);
public static boolean isValidRequest(SaRequest request, String... paramNames) {
return SaManager.getSaSignTemplate().isValidRequest(request, paramNames);
}
/**
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param request 待校验的请求对象
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
*/
public static void checkRequest(SaRequest request) {
SaManager.getSaSignTemplate().checkRequest(request);
public static void checkRequest(SaRequest request, String... paramNames) {
SaManager.getSaSignTemplate().checkRequest(request, paramNames);
}
}
@@ -15,22 +15,28 @@
*/
package cn.dev33.satoken.stp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import java.util.Map;
/**
* 快速、简洁的构建:调用 `StpUtil.login()` 时的 [ 配置参数 SaLoginModel ]
* <h2> 请更换为 new SaLoginParameter() </h2>
*
* 快速、简洁的构建:调用 `StpUtil.login()` 时的 [ 配置参数 SaLoginParameter ]
*
* <pre>
* // 例如:在登录时指定 token 有效期为七天,代码如下:
* StpUtil.login(10001, SaLoginConfig.setTimeout(60 * 60 * 24 * 7));
*
* // 上面的代码与下面的代码等价
* StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
* StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));
* </pre>
*
* @author click33
* @since 1.29.0
*/
@Deprecated
public class SaLoginConfig {
private SaLoginConfig() {
@@ -40,15 +46,15 @@ public class SaLoginConfig {
* @param device 此次登录的客户端设备类型
* @return 登录参数 Model
*/
public static SaLoginModel setDevice(String device) {
return create().setDevice(device);
public static SaLoginParameter setDevice(String device) {
return create().setDeviceType(device);
}
/**
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
* @return 登录参数 Model
*/
public static SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
public static SaLoginParameter setIsLastingCookie(Boolean isLastingCookie) {
return create().setIsLastingCookie(isLastingCookie);
}
@@ -56,7 +62,7 @@ public class SaLoginConfig {
* @param timeout 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
* @return 登录参数 Model
*/
public static SaLoginModel setTimeout(long timeout) {
public static SaLoginParameter setTimeout(long timeout) {
return create().setTimeout(timeout);
}
@@ -64,7 +70,7 @@ public class SaLoginConfig {
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,自动取全局配置的 activeTimeout 值)
* @return 对象自身
*/
public static SaLoginModel setActiveTimeout(long activeTimeout) {
public static SaLoginParameter setActiveTimeout(long activeTimeout) {
return create().setActiveTimeout(activeTimeout);
}
@@ -72,7 +78,7 @@ public class SaLoginConfig {
* @param extraData 扩展信息(只在jwt模式下生效)
* @return 登录参数 Model
*/
public static SaLoginModel setExtraData(Map<String, Object> extraData) {
public static SaLoginParameter setExtraData(Map<String, Object> extraData) {
return create().setExtraData(extraData);
}
@@ -80,7 +86,7 @@ public class SaLoginConfig {
* @param token 预定Token(预定本次登录生成的Token值)
* @return 登录参数 Model
*/
public static SaLoginModel setToken(String token) {
public static SaLoginParameter setToken(String token) {
return create().setToken(token);
}
@@ -90,7 +96,7 @@ public class SaLoginConfig {
* @param value 值
* @return 登录参数 Model
*/
public static SaLoginModel setExtra(String key, Object value) {
public static SaLoginParameter setExtra(String key, Object value) {
return create().setExtra(key, value);
}
@@ -98,7 +104,7 @@ public class SaLoginConfig {
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
* @return 登录参数 Model
*/
public static SaLoginModel setIsWriteHeader(Boolean isWriteHeader) {
public static SaLoginParameter setIsWriteHeader(Boolean isWriteHeader) {
return create().setIsWriteHeader(isWriteHeader);
}
@@ -108,16 +114,16 @@ public class SaLoginConfig {
* @param tokenSignTag /
* @return 登录参数 Model
*/
public static SaLoginModel setTokenSignTag(Object tokenSignTag) {
return create().setTokenSignTag(tokenSignTag);
public static SaLoginParameter setTokenSignTag(Map<String, Object> tokenSignTag) {
return create().setTerminalExtraData(tokenSignTag);
}
/**
* 静态方法获取一个 SaLoginModel 对象
* @return SaLoginModel 对象
* 静态方法获取一个 SaLoginParameter 对象
* @return SaLoginParameter 对象
*/
public static SaLoginModel create() {
return new SaLoginModel();
public static SaLoginParameter create() {
return new SaLoginParameter(SaManager.getConfig());
}
}
@@ -15,347 +15,16 @@
*/
package cn.dev33.satoken.stp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaTokenConsts;
import java.util.LinkedHashMap;
import java.util.Map;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
/**
* <h2> 请更改为 SaLoginParameter </h2>
* 在调用 `StpUtil.login()` 时的 配置参数 Model,决定登录的一些细节行为 <br>
*
* <pre>
* // 例如:在登录时指定 token 有效期为七天,代码如下:
* StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
* </pre>
*
* @author click33
* @since 1.13.2
*/
public class SaLoginModel {
/**
* 此次登录的客户端设备类型
*/
public String device;
/**
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
public Boolean isLastingCookie = true;
/**
* 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
*/
public Long timeout;
/**
* 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
*/
private Long activeTimeout;
/**
* 扩展信息(只在jwt模式下生效)
*/
public Map<String, Object> extraData;
/**
* 预定Token(预定本次登录生成的Token值)
*/
public String token;
/** 是否在登录后将 Token 写入到响应头 */
private Boolean isWriteHeader;
/** 本次登录挂载到 TokenSign 的数据 */
private Object tokenSignTag;
/**
* @return 此次登录的客户端设备类型
*/
public String getDevice() {
return device;
}
/**
* @param device 此次登录的客户端设备类型
* @return 对象自身
*/
public SaLoginModel setDevice(String device) {
this.device = device;
return this;
}
/**
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
public Boolean getIsLastingCookie() {
return isLastingCookie;
}
/**
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
* @return 对象自身
*/
public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
this.isLastingCookie = isLastingCookie;
return this;
}
/**
* @return 指定此次登录 token 有效期,单位:秒
*/
public Long getTimeout() {
return timeout;
}
/**
* @param timeout 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
* @return 对象自身
*/
public SaLoginModel setTimeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* @return 此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
*/
public Long getActiveTimeout() {
return activeTimeout;
}
/**
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
* @return 对象自身
*/
public SaLoginModel setActiveTimeout(long activeTimeout) {
this.activeTimeout = activeTimeout;
return this;
}
/**
* @return 扩展信息(只在jwt模式下生效)
*/
public Map<String, Object> getExtraData() {
return extraData;
}
/**
* @param extraData 扩展信息(只在jwt模式下生效)
* @return 对象自身
*/
public SaLoginModel setExtraData(Map<String, Object> extraData) {
this.extraData = extraData;
return this;
}
/**
* @return 预定Token(预定本次登录生成的Token值)
*/
public String getToken() {
return token;
}
/**
* @param token 预定Token(预定本次登录生成的Token值)
* @return 对象自身
*/
public SaLoginModel setToken(String token) {
this.token = token;
return this;
}
/**
* @return 是否在登录后将 Token 写入到响应头
*/
public Boolean getIsWriteHeader() {
return isWriteHeader;
}
/**
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
* @return 对象自身
*/
public SaLoginModel setIsWriteHeader(Boolean isWriteHeader) {
this.isWriteHeader = isWriteHeader;
return this;
}
/**
* 获取 本次登录挂载到 TokenSign 的数据
*
* @return tokenSignTag 本次登录挂载到 TokenSign 的数据
*/
public Object getTokenSignTag() {
return this.tokenSignTag;
}
/**
* 设置 本次登录挂载到 TokenSign 的数据
*
* @param tokenSignTag 本次登录挂载到 TokenSign 的数据
* @return 对象自身
*/
public SaLoginModel setTokenSignTag(Object tokenSignTag) {
this.tokenSignTag = tokenSignTag;
return this;
}
/*
* toString
*/
@Override
public String toString() {
return "SaLoginModel ["
+ "device=" + device
+ ", isLastingCookie=" + isLastingCookie
+ ", timeout=" + timeout
+ ", activeTimeout=" + activeTimeout
+ ", extraData=" + extraData
+ ", token=" + token
+ ", isWriteHeader=" + isWriteHeader
+ ", tokenSignTag=" + tokenSignTag
+ "]";
}
// ------ 附加方法
/**
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
public Boolean getIsLastingCookieOrFalse() {
if(isLastingCookie == null) {
return false;
}
return isLastingCookie;
}
/**
* 写入扩展数据(只在jwt模式下生效)
* @param key 键
* @param value 值
* @return 对象自身
*/
public SaLoginModel setExtra(String key, Object value) {
if(this.extraData == null) {
this.extraData = new LinkedHashMap<>();
}
this.extraData.put(key, value);
return this;
}
/**
* 获取扩展数据(只在jwt模式下生效)
* @param key 键
* @return 扩展数据的值
*/
public Object getExtra(String key) {
if(this.extraData == null) {
return null;
}
return this.extraData.get(key);
}
/**
* 判断是否设置了扩展数据
* @return /
*/
public boolean isSetExtraData() {
return extraData != null && extraData.size() != 0;
}
/**
* @return Cookie时长
*/
public int getCookieTimeout() {
if( ! getIsLastingCookieOrFalse()) {
return -1;
}
long _timeout = getTimeout();
if(_timeout == SaTokenDao.NEVER_EXPIRE || _timeout > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int)_timeout;
}
/**
* @return 获取device参数,如果为null,则返回默认值
*/
public String getDeviceOrDefault() {
if(device == null) {
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
}
return device;
}
/**
* 构建对象,初始化默认值
* @return 对象自身
*/
public SaLoginModel build() {
return build(SaManager.getConfig());
}
/**
* 构建对象,初始化默认值
* @param config 配置对象
* @return 对象自身
*/
public SaLoginModel build(SaTokenConfig config) {
// if(device == null) {
// device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
// }
// if(isLastingCookie == null) {
// isLastingCookie = true;
// }
if(timeout == null) {
timeout = config.getTimeout();
}
if(isWriteHeader == null) {
isWriteHeader = config.getIsWriteHeader();
}
return this;
}
/**
* 静态方法获取一个 SaLoginModel 对象
* @return SaLoginModel 对象
*/
public static SaLoginModel create() {
return new SaLoginModel();
}
// ---------------- 过期方法
/**
* 请改为 getTimeout
* @return timeout 值 (如果此配置项尚未配置,则取全局配置的值)
*/
@Deprecated
public Long getTimeoutOrGlobalConfig() {
if(timeout == null) {
timeout = SaManager.getConfig().getTimeout();
}
return timeout;
}
/**
* 请改为 getIsWriteHeader
* @return 是否在登录后将 Token 写入到响应头 (如果此配置项尚未配置,则取全局配置的值)
*/
@Deprecated
public Boolean getIsWriteHeaderOrGlobalConfig() {
if(isWriteHeader == null) {
isWriteHeader = SaManager.getConfig().getIsWriteHeader();
}
return isWriteHeader;
}
@Deprecated
public class SaLoginModel extends SaLoginParameter {
}
@@ -31,7 +31,7 @@ package cn.dev33.satoken.stp;
* "sessionTimeout": 2591977, // Account-Session剩余有效时间 (单位: 秒)
* "tokenSessionTimeout": -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
* "tokenActiveTimeout": -1, // Token 距离被冻结还剩多少时间 (单位: 秒)
* "loginDevice": "default-device" // 登录设备类型
* "loginDevice": "DEF" // 登录设备类型
* }
* </pre>
* </p>
@@ -69,7 +69,7 @@ public class SaTokenInfo {
public long tokenActiveTimeout;
/** 登录设备类型 */
public String loginDevice;
public String loginDeviceType;
/** 自定义数据(暂无意义,留作扩展) */
public String tag;
@@ -205,15 +205,15 @@ public class SaTokenInfo {
/**
* @return 登录设备类型
*/
public String getLoginDevice() {
return loginDevice;
public String getLoginDeviceType() {
return loginDeviceType;
}
/**
* @param loginDevice 登录设备类型
* @param loginDeviceType 登录设备类型
*/
public void setLoginDevice(String loginDevice) {
this.loginDevice = loginDevice;
public void setLoginDeviceType(String loginDeviceType) {
this.loginDeviceType = loginDeviceType;
}
/**
@@ -238,7 +238,7 @@ public class SaTokenInfo {
return "SaTokenInfo [tokenName=" + tokenName + ", tokenValue=" + tokenValue + ", isLogin=" + isLogin
+ ", loginId=" + loginId + ", loginType=" + loginType + ", tokenTimeout=" + tokenTimeout
+ ", sessionTimeout=" + sessionTimeout + ", tokenSessionTimeout=" + tokenSessionTimeout
+ ", tokenActiveTimeout=" + tokenActiveTimeout + ", loginDevice=" + loginDevice + ", tag=" + tag
+ ", tokenActiveTimeout=" + tokenActiveTimeout + ", loginDeviceType=" + loginDeviceType + ", tag=" + tag
+ "]";
}
File diff suppressed because it is too large Load Diff
@@ -17,9 +17,12 @@ package cn.dev33.satoken.stp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaTwoParamFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.session.SaTerminalInfo;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import java.util.List;
@@ -117,10 +120,19 @@ public class StpUtil {
* 在当前会话写入指定 token 值
*
* @param tokenValue token 值
* @param loginModel 登录参数
* @param loginParameter 登录参数
*/
public static void setTokenValue(String tokenValue, SaLoginModel loginModel){
stpLogic.setTokenValue(tokenValue, loginModel);
public static void setTokenValue(String tokenValue, SaLoginParameter loginParameter){
stpLogic.setTokenValue(tokenValue, loginParameter);
}
/**
* 将 token 写入到当前请求的 Storage 存储器里
*
* @param tokenValue 要保存的 token 值
*/
public static void setTokenValueToStorage(String tokenValue){
stpLogic.setTokenValueToStorage(tokenValue);
}
/**
@@ -168,10 +180,10 @@ public class StpUtil {
* 会话登录,并指定登录设备类型
*
* @param id 账号id,建议的类型:(long | int | String
* @param device 设备类型
* @param deviceType 设备类型
*/
public static void login(Object id, String device) {
stpLogic.login(id, device);
public static void login(Object id, String deviceType) {
stpLogic.login(id, deviceType);
}
/**
@@ -198,10 +210,10 @@ public class StpUtil {
* 会话登录,并指定所有登录参数 Model
*
* @param id 账号id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
* @param loginParameter 此次登录的参数Model
*/
public static void login(Object id, SaLoginModel loginModel) {
stpLogic.login(id, loginModel);
public static void login(Object id, SaLoginParameter loginParameter) {
stpLogic.login(id, loginParameter);
}
/**
@@ -218,11 +230,11 @@ public class StpUtil {
* 创建指定账号 id 的登录会话数据
*
* @param id 账号id,建议的类型:(long | int | String
* @param loginModel 此次登录的参数Model
* @param loginParameter 此次登录的参数Model
* @return 返回会话令牌
*/
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
public static String createLoginSession(Object id, SaLoginParameter loginParameter) {
return stpLogic.createLoginSession(id, loginParameter);
}
/**
@@ -235,7 +247,7 @@ public class StpUtil {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
// --- 注销 (根据 token)
/**
* 在当前客户端会话注销
@@ -245,26 +257,14 @@ public class StpUtil {
}
/**
* 会话注销,根据账号id
*
* @param loginId 账号id
* 在当前客户端会话注销,根据注销参数
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
public static void logout(SaLogoutParameter logoutParameter) {
stpLogic.logout(logoutParameter);
}
/**
* 会话注销,根据账号id 和 设备类型
*
* @param loginId 账号id
* @param device 设备类型 (填 null 代表注销该账号的所有设备类型)
*/
public static void logout(Object loginId, String device) {
stpLogic.logout(loginId, device);
}
/**
* 会话注销,根据指定 Token
* 注销下线,根据指定 token
*
* @param tokenValue 指定 token
*/
@@ -273,24 +273,13 @@ public class StpUtil {
}
/**
* 踢人下线,根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
* 注销下线,根据指定 token、注销参数
*
* @param loginId 账号id
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* 踢人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param device 设备类型 (填 null 代表踢出该账号的所有设备类型)
*/
public static void kickout(Object loginId, String device) {
stpLogic.kickout(loginId, device);
public static void logoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.logoutByTokenValue(tokenValue, logoutParameter);
}
/**
@@ -303,17 +292,163 @@ public class StpUtil {
stpLogic.kickoutByTokenValue(tokenValue);
}
/**
* 踢人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter 注销参数
*/
public static void kickoutByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.kickoutByTokenValue(tokenValue, logoutParameter);
}
/**
* 顶人下线,根据指定 token
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
*/
public static void replacedByTokenValue(String tokenValue) {
stpLogic.replacedByTokenValue(tokenValue);
}
/**
* 顶人下线,根据指定 token、注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param tokenValue 指定 token
* @param logoutParameter /
*/
public static void replacedByTokenValue(String tokenValue, SaLogoutParameter logoutParameter) {
stpLogic.replacedByTokenValue(tokenValue, logoutParameter);
}
// --- 注销 (根据 loginId)
/**
* 会话注销,根据账号id
*
* @param loginId 账号id
*/
public static void logout(Object loginId) {
stpLogic.logout(loginId);
}
/**
* 会话注销,根据账号id 和 设备类型
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表注销该账号的所有设备类型)
*/
public static void logout(Object loginId, String deviceType) {
stpLogic.logout(loginId, deviceType);
}
/**
* 会话注销,根据账号id 和 注销参数
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void logout(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.logout(loginId, logoutParameter);
}
/**
* 踢人下线,根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
*/
public static void kickout(Object loginId) {
stpLogic.kickout(loginId);
}
/**
* 踢人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param deviceType 设备类型 (填 null 代表踢出该账号的所有设备类型)
*/
public static void kickout(Object loginId, String deviceType) {
stpLogic.kickout(loginId, deviceType);
}
/**
* 踢人下线,根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5 </p>
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void kickout(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.kickout(loginId, logoutParameter);
}
/**
* 顶人下线,根据账号id
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
*/
public static void replaced(Object loginId) {
stpLogic.replaced(loginId);
}
/**
* 顶人下线,根据账号id 和 设备类型
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param device 设备类型 (填 null 代表顶替该账号的所有设备类型)
* @param deviceType 设备类型 (填 null 代表顶替该账号的所有设备类型)
*/
public static void replaced(Object loginId, String device) {
stpLogic.replaced(loginId, device);
public static void replaced(Object loginId, String deviceType) {
stpLogic.replaced(loginId, deviceType);
}
/**
* 顶人下线,根据账号id 和 注销参数
* <p> 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4 </p>
*
* @param loginId 账号id
* @param logoutParameter 注销参数
*/
public static void replaced(Object loginId, SaLogoutParameter logoutParameter) {
stpLogic.replaced(loginId, logoutParameter);
}
// --- 注销 (会话管理辅助方法)
/**
* 在 Account-Session 上移除 Terminal 信息 (注销下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByLogout(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByLogout(session, terminal);
}
/**
* 在 Account-Session 上移除 Terminal 信息 (踢人下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByKickout(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByKickout(session, terminal);
}
/**
* 在 Account-Session 上移除 Terminal 信息 (顶人下线方式)
* @param session /
* @param terminal /
*/
public static void removeTerminalByReplaced(SaSession session, SaTerminalInfo terminal) {
stpLogic.removeTerminalByReplaced(session, terminal);
}
// 会话查询
/**
@@ -398,7 +533,7 @@ public class StpUtil {
}
/**
* 获取指定 token 对应的账号id,如果未登录,则返回 null
* 获取指定 token 对应的账号id,如果 token 无效或 token 处于被踢、被顶、被冻结等状态,则返回 null
*
* @param tokenValue token
* @return 账号id
@@ -407,6 +542,16 @@ public class StpUtil {
return stpLogic.getLoginIdByToken(tokenValue);
}
/**
* 获取指定 token 对应的账号id,如果 token 无效或 token 处于被踢、被顶等状态 (不考虑被冻结),则返回 null
*
* @param tokenValue token
* @return 账号id
*/
public Object getLoginIdByTokenNotThinkFreeze(String tokenValue) {
return stpLogic.getLoginIdByTokenNotThinkFreeze(tokenValue);
}
/**
* 获取当前 Token 的扩展信息(此函数只在jwt模式下生效)
*
@@ -806,11 +951,11 @@ public class StpUtil {
* </p>
*
* @param loginId 账号id
* @param device 设备类型,填 null 代表不限设备类型
* @param deviceType 设备类型,填 null 代表不限设备类型
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId, String device) {
return stpLogic.getTokenValueByLoginId(loginId, device);
public static String getTokenValueByLoginId(Object loginId, String deviceType) {
return stpLogic.getTokenValueByLoginId(loginId, deviceType);
}
/**
@@ -827,22 +972,42 @@ public class StpUtil {
* 获取指定账号 id 指定设备类型端的 token 集合
*
* @param loginId 账号id
* @param device 设备类型,填 null 代表不限设备类型
* @param deviceType 设备类型,填 null 代表不限设备类型
* @return 此 loginId 的所有登录 token
*/
public static List<String> getTokenValueListByLoginId(Object loginId, String device) {
return stpLogic.getTokenValueListByLoginId(loginId, device);
public static List<String> getTokenValueListByLoginId(Object loginId, String deviceType) {
return stpLogic.getTokenValueListByLoginId(loginId, deviceType);
}
/**
* 获取指定账号 id 指定设备类型端的 tokenSign 集合
* 获取指定账号 id 已登录设备信息集合
*
* @param loginId 账号id
* @param device 设备类型,填 null 代表不限设备类型
* @return 此 loginId 的所有登录 tokenSign
* @return 此 loginId 的所有登录 token
*/
public static List<TokenSign> getTokenSignListByLoginId(Object loginId, String device) {
return stpLogic.getTokenSignListByLoginId(loginId, device);
public static List<SaTerminalInfo> getTerminalListByLoginId(Object loginId) {
return stpLogic.getTerminalListByLoginId(loginId);
}
/**
* 获取指定账号 id 指定设备类型端的已登录设备信息集合
*
* @param loginId 账号id
* @param deviceType 设备类型,填 null 代表不限设备类型
* @return /
*/
public static List<SaTerminalInfo> getTerminalListByLoginId(Object loginId, String deviceType) {
return stpLogic.getTerminalListByLoginId(loginId, deviceType);
}
/**
* 获取指定账号 id 已登录设备信息集合,执行特定函数
*
* @param loginId 账号id
* @param function 需要执行的函数
*/
public static void forEachTerminalList(Object loginId, SaTwoParamFunction<SaSession, SaTerminalInfo> function) {
stpLogic.forEachTerminalList(loginId, function);
}
/**
@@ -850,8 +1015,8 @@ public class StpUtil {
*
* @return 当前令牌的登录设备类型
*/
public static String getLoginDevice() {
return stpLogic.getLoginDevice();
public static String getLoginDeviceType() {
return stpLogic.getLoginDeviceType();
}
/**
@@ -860,8 +1025,8 @@ public class StpUtil {
* @param tokenValue 指定token
* @return 当前令牌的登录设备类型
*/
public static String getLoginDeviceByToken(String tokenValue) {
return stpLogic.getLoginDeviceByToken(tokenValue);
public static String getLoginDeviceTypeByToken(String tokenValue) {
return stpLogic.getLoginDeviceTypeByToken(tokenValue);
}
/**
@@ -873,6 +1038,15 @@ public class StpUtil {
return stpLogic.getTokenLastActiveTime();
}
/**
* 判断对于指定 loginId 来讲,指定设备 id 是否为可信任设备
* @param deviceId /
* @return /
*/
public static boolean isTrustDeviceId(Object userId, String deviceId) {
return stpLogic.isTrustDeviceId(userId, deviceId);
}
// ------------------- 会话管理 -------------------
@@ -1260,4 +1434,42 @@ public class StpUtil {
stpLogic.closeSafe(service);
}
// ------------------- Bean 对象、字段代理 -------------------
/**
* 根据当前配置对象创建一个 SaLoginParameter 对象
*
* @return /
*/
public static SaLoginParameter createSaLoginParameter() {
return stpLogic.createSaLoginParameter();
}
// ------------------- 过期方法 -------------------
/**
* <h2>请更换为 getLoginDeviceType </h2>
* 返回当前会话的登录设备类型
*
* @return 当前令牌的登录设备类型
*/
@Deprecated
public static String getLoginDevice() {
return stpLogic.getLoginDevice();
}
/**
* <h2>请更换为 getLoginDeviceTypeByToken </h2>
* 返回指定 token 会话的登录设备类型
*
* @param tokenValue 指定token
* @return 当前令牌的登录设备类型
*/
@Deprecated
public static String getLoginDeviceByToken(String tokenValue) {
return stpLogic.getLoginDeviceByToken(tokenValue);
}
}
@@ -0,0 +1,620 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaCookieConfig;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaReplacedRange;
import cn.dev33.satoken.util.SaTokenConsts;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 在调用 `StpUtil.login()` 时的 配置参数对象,决定登录的一些细节行为 <br>
*
* <pre>
* // 例如:在登录时指定 token 有效期为七天,代码如下:
* StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));
* </pre>
*
* @author click33
* @since 1.13.2
*/
public class SaLoginParameter {
// --------- 单独参数
/**
* 此次登录的客户端设备类型
*/
private String deviceType;
/**
* 此次登录的客户端设备id
*/
private String deviceId;
/**
* 扩展信息(只在 jwt 模式下生效)
*/
private Map<String, Object> extraData;
/**
* 预定Token(预定本次登录生成的Token值)
*/
private String token;
/**
* 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
*/
private Map<String, Object> terminalExtraData;
// --------- 覆盖性参数
/**
* 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
*/
private long timeout;
/**
* 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
*/
private Long activeTimeout;
/**
* 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
*/
private Boolean isConcurrent;
/**
* 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
*/
private Boolean isShare;
/**
* 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
*/
private int maxLoginCount;
/**
* 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
*/
private int maxTryTimes;
/**
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
private Boolean isLastingCookie;
/**
* 是否在登录后将 Token 写入到响应头
*/
private Boolean isWriteHeader;
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*/
private SaReplacedRange replacedRange;
/**
* 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*/
private SaLogoutMode overflowLogoutMode;
/**
* 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*/
private Boolean rightNowCreateTokenSession;
/**
* Cookie 配置对象
*/
public SaCookieConfig cookie = new SaCookieConfig();
// ------ 附加方法
public SaLoginParameter() {
this(SaManager.getConfig());
}
public SaLoginParameter(SaTokenConfig config) {
setDefaultValues(config);
}
/**
* 根据 SaTokenConfig 对象初始化默认值
*
* @param config 使用的配置对象
* @return 对象自身
*/
public SaLoginParameter setDefaultValues(SaTokenConfig config) {
this.deviceType = SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE;
this.timeout = config.getTimeout();
this.isConcurrent = config.getIsConcurrent();
this.isShare = config.getIsShare();
this.maxLoginCount = config.getMaxLoginCount();
this.maxTryTimes = config.getMaxTryTimes();
this.isLastingCookie = config.getIsLastingCookie();
this.isWriteHeader = config.getIsWriteHeader();
this.replacedRange = config.getReplacedRange();
this.overflowLogoutMode = config.getOverflowLogoutMode();
this.rightNowCreateTokenSession = config.getRightNowCreateTokenSession();
this.setupCookieConfig(cookie -> {
SaCookieConfig gCookie = config.getCookie();
cookie.setDomain(gCookie.getDomain());
cookie.setPath(gCookie.getPath());
cookie.setSecure(gCookie.getSecure());
cookie.setHttpOnly(gCookie.getHttpOnly());
cookie.setSameSite(gCookie.getSameSite());
cookie.setExtraAttrs(new LinkedHashMap<>(gCookie.getExtraAttrs()));
});
return this;
}
/**
* 写入扩展数据(只在jwt模式下生效)
* @param key 键
* @param value 值
* @return 对象自身
*/
public SaLoginParameter setExtra(String key, Object value) {
if(this.extraData == null) {
this.extraData = new LinkedHashMap<>();
}
this.extraData.put(key, value);
return this;
}
/**
* 获取扩展数据(只在jwt模式下生效)
* @param key 键
* @return 扩展数据的值
*/
public Object getExtra(String key) {
if(this.extraData == null) {
return null;
}
return this.extraData.get(key);
}
/**
* 判断是否设置了扩展数据(只在jwt模式下生效)
* @return /
*/
public boolean haveExtraData() {
return extraData != null && !extraData.isEmpty();
}
/**
* 写入本次登录挂载到 SaTerminalInfo 的自定义扩展数据
* @param key 键
* @param value 值
* @return 对象自身
*/
public SaLoginParameter setTerminalExtra(String key, Object value) {
if(this.terminalExtraData == null) {
this.terminalExtraData = new LinkedHashMap<>();
}
this.terminalExtraData.put(key, value);
return this;
}
/**
* 获取本次登录挂载到 SaTerminalInfo 的自定义扩展数据
* @param key 键
* @return 扩展数据的值
*/
public Object getTerminalExtra(String key) {
if(this.terminalExtraData == null) {
return null;
}
return this.terminalExtraData.get(key);
}
/**
* 判断是否设置了本次登录挂载到 SaTerminalInfo 的自定义扩展数据
* @return /
*/
public boolean haveTerminalExtraData() {
return terminalExtraData != null && !terminalExtraData.isEmpty();
}
/**
* 计算 Cookie 时长
* @return /
*/
public int getCookieTimeout() {
if( ! getIsLastingCookie()) {
return -1;
}
long _timeout = getTimeout();
if(_timeout == SaTokenDao.NEVER_EXPIRE || _timeout > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int)_timeout;
}
/**
* 静态方法获取一个 SaLoginParameter 对象
* @return SaLoginParameter 对象
*/
public static SaLoginParameter create() {
return new SaLoginParameter(SaManager.getConfig());
}
/**
* 设置 Cookie 配置项
* @param fun /
* @return 对象自身
*/
public SaLoginParameter setupCookieConfig(SaParamFunction<SaCookieConfig> fun) {
fun.run(this.cookie);
return this;
}
// ---------------- get set
/**
* @return 此次登录的客户端设备类型
*/
public String getDeviceType() {
return deviceType;
}
/**
* @param deviceType 此次登录的客户端设备类型
* @return 对象自身
*/
public SaLoginParameter setDeviceType(String deviceType) {
this.deviceType = deviceType;
return this;
}
/**
* 获取 此次登录的客户端设备id
*
* @return deviceId 此次登录的客户端设备id
*/
public String getDeviceId() {
return this.deviceId;
}
/**
* 设置 此次登录的客户端设备id
*
* @param deviceId 此次登录的客户端设备id
*/
public SaLoginParameter setDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*
* @return replacedMode 顶人下线的范围
*/
public SaReplacedRange getReplacedRange() {
return this.replacedRange;
}
/**
* 当 isConcurrent=false 时,顶人下线的范围 (CURR_DEVICE_TYPE=当前指定的设备类型端, ALL_DEVICE_TYPE=所有设备类型端)
*
* @param replacedRange /
* @return 对象自身
*/
public SaLoginParameter setReplacedRange(SaReplacedRange replacedRange) {
this.replacedRange = replacedRange;
return this;
}
/**
* 获取 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*
* @return overflowLogoutMode /
*/
public SaLogoutMode getOverflowLogoutMode() {
return this.overflowLogoutMode;
}
/**
* 设置 溢出 maxLoginCount 的客户端,将以何种方式注销下线 (LOGOUT=注销下线, KICKOUT=踢人下线, REPLACED=顶人下线)
*
* @param overflowLogoutMode /
* @return 对象自身
*/
public SaLoginParameter setOverflowLogoutMode(SaLogoutMode overflowLogoutMode) {
this.overflowLogoutMode = overflowLogoutMode;
return this;
}
/**
* @return 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
*/
public Boolean getIsLastingCookie() {
return isLastingCookie;
}
/**
* @param isLastingCookie 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
* @return 对象自身
*/
public SaLoginParameter setIsLastingCookie(Boolean isLastingCookie) {
this.isLastingCookie = isLastingCookie;
return this;
}
/**
* @return 指定此次登录 token 有效期,单位:秒
*/
public long getTimeout() {
return timeout;
}
/**
* @param timeout 指定此次登录 token 有效期,单位:秒 (如未指定,自动取全局配置的 timeout 值)
* @return 对象自身
*/
public SaLoginParameter setTimeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* @return 此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
*/
public Long getActiveTimeout() {
return activeTimeout;
}
/**
* @param activeTimeout 指定此次登录 token 最低活跃频率,单位:秒(如未指定,则使用全局配置的 activeTimeout 值)
* @return 对象自身
*/
public SaLoginParameter setActiveTimeout(long activeTimeout) {
this.activeTimeout = activeTimeout;
return this;
}
/**
* @return 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
*/
public Boolean getIsConcurrent() {
return isConcurrent;
}
/**
* @param isConcurrent 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
* @return 对象自身
*/
public SaLoginParameter setIsConcurrent(Boolean isConcurrent) {
this.isConcurrent = isConcurrent;
return this;
}
/**
* @return 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个token, 为 false 时每次登录新建一个 token)
*/
public Boolean getIsShare() {
return isShare;
}
/**
* @param isShare 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个token, 为 false 时每次登录新建一个 token)
* @return 对象自身
*/
public SaLoginParameter setIsShare(Boolean isShare) {
this.isShare = isShare;
return this;
}
/**
* @return 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
*/
public int getMaxLoginCount() {
return maxLoginCount;
}
/**
* @param maxLoginCount 同一账号最大登录数量,-1代表不限 (只有在 isConcurrent=true, isShare=false 时此配置项才有意义)
* @return 对象自身
*/
public SaLoginParameter setMaxLoginCount(int maxLoginCount) {
this.maxLoginCount = maxLoginCount;
return this;
}
/**
* @return 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
*/
public int getMaxTryTimes() {
return maxTryTimes;
}
/**
* @param maxTryTimes 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用)
* @return 对象自身
*/
public SaLoginParameter setMaxTryTimes(int maxTryTimes) {
this.maxTryTimes = maxTryTimes;
return this;
}
/**
* @return 扩展信息(只在jwt模式下生效)
*/
public Map<String, Object> getExtraData() {
return extraData;
}
/**
* @param extraData 扩展信息(只在jwt模式下生效)
* @return 对象自身
*/
public SaLoginParameter setExtraData(Map<String, Object> extraData) {
this.extraData = extraData;
return this;
}
/**
* @return 预定Token(预定本次登录生成的Token值)
*/
public String getToken() {
return token;
}
/**
* @param token 预定Token(预定本次登录生成的Token值)
* @return 对象自身
*/
public SaLoginParameter setToken(String token) {
this.token = token;
return this;
}
/**
* @return 是否在登录后将 Token 写入到响应头
*/
public Boolean getIsWriteHeader() {
return isWriteHeader;
}
/**
* @param isWriteHeader 是否在登录后将 Token 写入到响应头
* @return 对象自身
*/
public SaLoginParameter setIsWriteHeader(Boolean isWriteHeader) {
this.isWriteHeader = isWriteHeader;
return this;
}
/**
* 获取 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
*
* @return /
*/
public Map<String, Object> getTerminalExtraData() {
return this.terminalExtraData;
}
/**
* 设置 本次登录挂载到 SaTerminalInfo 的自定义扩展数据
*
* @param terminalExtraData /
* @return 对象自身
*/
public SaLoginParameter setTerminalExtraData(Map<String, Object> terminalExtraData) {
this.terminalExtraData = terminalExtraData;
return this;
}
/**
* 获取 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*
* @return /
*/
public Boolean getRightNowCreateTokenSession() {
return this.rightNowCreateTokenSession;
}
/**
* 设置 在登录时,是否立即创建对应的 Token-Session true=在登录时立即创建,false=在第一次调用 getTokenSession() 时创建)
*
* @param rightNowCreateTokenSession /
* @return 对象自身
*/
public SaLoginParameter setRightNowCreateTokenSession(Boolean rightNowCreateTokenSession) {
this.rightNowCreateTokenSession = rightNowCreateTokenSession;
return this;
}
/**
* @return Cookie 配置对象
*/
public SaCookieConfig getCookie() {
return cookie;
}
/**
* @param cookie Cookie 配置对象
* @return 对象自身
*/
public SaLoginParameter setCookie(SaCookieConfig cookie) {
this.cookie = cookie;
return this;
}
/*
* toString
*/
@Override
public String toString() {
return "SaLoginParameter ["
+ "deviceType=" + deviceType
+ ", deviceId=" + deviceId
+ ", replacedRange=" + replacedRange
+ ", overflowLogoutMode=" + overflowLogoutMode
+ ", isLastingCookie=" + isLastingCookie
+ ", timeout=" + timeout
+ ", activeTimeout=" + activeTimeout
+ ", isConcurrent=" + isConcurrent
+ ", isShare=" + isShare
+ ", maxLoginCount=" + maxLoginCount
+ ", maxTryTimes=" + maxTryTimes
+ ", extraData=" + extraData
+ ", token=" + token
+ ", isWriteHeader=" + isWriteHeader
+ ", terminalTag=" + terminalExtraData
+ ", rightNowCreateTokenSession=" + rightNowCreateTokenSession
+ ", cookie=" + cookie
+ "]";
}
/**
* <h2> 请更换为 getDeviceType </h2>
* @return 此次登录的客户端设备类型
*/
@Deprecated
public String getDevice() {
return deviceType;
}
/**
* <h2> 请更换为 setDeviceType </h2>
* @param device 此次登录的客户端设备类型
* @return 对象自身
*/
@Deprecated
public SaLoginParameter setDevice(String device) {
this.deviceType = device;
return this;
}
}
@@ -0,0 +1,218 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutMode;
import cn.dev33.satoken.stp.parameter.enums.SaLogoutRange;
/**
* 在会话注销时的 配置参数对象,决定注销时的一些细节行为 <br>
*
* <pre>
* // 例如:
* StpUtil.logout(10001, new SaLogoutParameter());
* </pre>
*
* @author click33
* @since 1.41.0
*/
public class SaLogoutParameter {
// --------- 单独参数
/**
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
*/
private String deviceType;
/**
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
*/
private SaLogoutMode mode = SaLogoutMode.LOGOUT;
// --------- 覆盖性参数
/**
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
*/
private SaLogoutRange range;
/**
* 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
*/
private Boolean isKeepFreezeOps;
/**
* 在注销 token 后,是否保留其对应的 Token-Session
*/
private Boolean isKeepTokenSession;
// ------ 附加方法
public SaLogoutParameter() {
this(SaManager.getConfig());
}
public SaLogoutParameter(SaTokenConfig config) {
setDefaultValues(config);
}
/**
* 根据 SaTokenConfig 对象初始化默认值
*
* @param config 使用的配置对象
* @return 对象自身
*/
public SaLogoutParameter setDefaultValues(SaTokenConfig config) {
this.range = config.getLogoutRange();
this.isKeepFreezeOps = config.getIsLogoutKeepFreezeOps();
this.isKeepTokenSession = config.getIsLogoutKeepTokenSession();
return this;
}
/**
* 静态方法获取一个 SaLoginParameter 对象
* @return SaLoginParameter 对象
*/
public static SaLogoutParameter create() {
return new SaLogoutParameter(SaManager.getConfig());
}
// ---------------- get set
/**
* @return 在注销 token 后,是否保留其对应的 Token-Session
*/
public Boolean getIsKeepTokenSession() {
return isKeepTokenSession;
}
/**
* @param isKeepTokenSession 在注销 token 后,是否保留其对应的 Token-Session
*
* @return 对象自身
*/
public SaLogoutParameter setIsKeepTokenSession(Boolean isKeepTokenSession) {
this.isKeepTokenSession = isKeepTokenSession;
return this;
}
/**
* 获取 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
*
* @return /
*/
public Boolean getIsKeepFreezeOps() {
return this.isKeepFreezeOps;
}
/**
* 设置 如果 token 已被冻结,是否保留其操作权 (是否允许此 token 调用注销API)
* <br/> (此参数只在调用 StpUtil.[logout/kickout/replaced]ByTokenValue("token") 时有效)
*
* @param isKeepFreezeOps /
* @return 对象自身
*/
public SaLogoutParameter setIsKeepFreezeOps(Boolean isKeepFreezeOps) {
this.isKeepFreezeOps = isKeepFreezeOps;
return this;
}
/**
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
*
* @return deviceType /
*/
public String getDeviceType() {
return this.deviceType;
}
/**
* 需要注销的设备类型 (如果不指定,则默认注销所有客户端)
*
* @param deviceType /
* @return /
*/
public SaLogoutParameter setDeviceType(String deviceType) {
this.deviceType = deviceType;
return this;
}
/**
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
*
* @return logoutMode 注销类型
*/
public SaLogoutMode getMode() {
return this.mode;
}
/**
* 注销类型 (LOGOUT=注销下线、KICKOUT=踢人下线,REPLACED=顶人下线)
*
* @param mode 注销类型
* @return /
*/
public SaLogoutParameter setMode(SaLogoutMode mode) {
this.mode = mode;
return this;
}
/**
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
*
* @return /
*/
public SaLogoutRange getRange() {
return this.range;
}
/**
* 注销范围 (TOKEN=只注销当前 token 的会话,ACCOUNT=注销当前 token 指向的 loginId 其所有客户端会话)
* <br/> (此参数只在调用 StpUtil.logout() 时有效)
*
* @param range /
* @return /
*/
public SaLogoutParameter setRange(SaLogoutRange range) {
this.range = range;
return this;
}
/*
* toString
*/
@Override
public String toString() {
return "SaLoginParameter ["
+ "deviceType=" + deviceType
+ ", isKeepTokenSession=" + isKeepTokenSession
+ ", isKeepFreezeOps=" + isKeepFreezeOps
+ ", mode=" + mode
+ ", range=" + range
+ "]";
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* SaLogoutMode: 注销模式
*
* @author click33
* @since 1.41.0
*/
public enum SaLogoutMode {
/**
* 注销下线
*/
LOGOUT,
/**
* 踢人下线
*/
KICKOUT,
/**
* 顶人下线
*/
REPLACED;
}
@@ -0,0 +1,36 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* SaLogoutMode: 注销范围
*
* @author click33
* @since 1.41.0
*/
public enum SaLogoutRange {
/**
* token 范围:只注销提供的 token 指向的会话
*/
TOKEN,
/**
* 账号范围:注销 token 指向的 loginId 会话
*/
ACCOUNT
}
@@ -0,0 +1,36 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.stp.parameter.enums;
/**
* 顶人下线的范围
*
* @author click33
* @since 1.41.0
*/
public enum SaReplacedRange {
/**
* 当前指定的设备类型端
*/
CURR_DEVICE_TYPE,
/**
* 所有设备类型端
*/
ALL_DEVICE_TYPE
}
@@ -65,6 +65,8 @@ public final class SaAnnotationStrategy {
annotationHandlerMap.put(SaCheckHttpBasic.class, new SaCheckHttpBasicHandler());
annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler());
annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler());
annotationHandlerMap.put(SaCheckSign.class, new SaCheckSignHandler());
annotationHandlerMap.put(SaCheckApiKey.class, new SaCheckApiKeyHandler());
}
/**

Some files were not shown because too many files have changed in this diff Show More