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

Compare commits

...

64 Commits

Author SHA1 Message Date
click33 3372cf190e v1.24.0 更新 2021-07-24 20:39:27 +08:00
click33 d4493d0f98 优化单点登录模块Path判断 2021-07-24 02:03:17 +08:00
click33 5d5e2a5d52 新增OAuth2.0模块常用方法说明 2021-07-24 01:49:23 +08:00
click33 64beb7a18a 新增Sa-Id模块,解决微服务内部调用鉴权问题 2021-07-24 01:10:46 +08:00
省长 bffdd0f2e1 !62 update README.md.
Merge pull request !62 from 不忘初心/N/A
2021-07-23 15:49:25 +00:00
不忘初心 fef0d8f3e2 update README.md. 2021-07-23 15:48:55 +00:00
click33 bc8339e13d 新增OAuth2.0接口 2021-07-23 03:21:03 +08:00
click33 bea2592dc9 晚上单点登录文档 2021-07-23 02:13:44 +08:00
click33 cf93324053 优化Readme 2021-07-23 00:30:36 +08:00
click33 cbb7713c9f 优化文档 2021-07-23 00:19:58 +08:00
省长 010f4d3c88 !61 docs 向README添加helio脚手架链接
Merge pull request !61 from Uncarbon/N/A
2021-07-22 12:43:48 +00:00
Uncarbon 2842a7a56e docs 向README添加helio脚手架链接 2021-07-22 12:43:08 +00:00
click33 ef1507e5b7 优化文档 2021-07-22 20:36:46 +08:00
click33 95beaee6ee 完善文档:Session模型详解 2021-07-22 04:22:59 +08:00
click33 cbc28d392b 优化文档 2021-07-21 14:39:50 +08:00
click33 23a9fb3447 优化文档 2021-07-21 02:07:19 +08:00
省长 93714d28e0 update README.md. 2021-07-20 16:57:01 +00:00
click33 b5f23f2455 增加对SpringBoot1.x版本的兼容性 2021-07-19 22:18:40 +08:00
click33 c7ca8ee280 v1.23.0 更新 2021-07-19 01:46:09 +08:00
click33 34d1008499 v1.23.0 更新 2021-07-19 01:23:35 +08:00
click33 de7ccf05aa OAuth2.0文档 2021-07-19 01:12:55 +08:00
click33 6d44299902 优化文档 2021-07-18 00:02:57 +08:00
click33 ee80633582 修正错误提交 2021-07-17 23:55:21 +08:00
click33 971c2860f0 OAuth2.0 beta .. 2021-07-17 23:48:12 +08:00
click33 742b65366a OAuth2.0模块 beta 2021-07-17 23:14:23 +08:00
省长 93e231ff18 !57 update sa-token-doc/doc/use/dao-extend.md.
Merge pull request !57 from AppleOfGray/N/A
2021-07-16 20:47:24 +00:00
省长 ac35d77f3b !58 update sa-token-doc/doc/use/kick.md.
Merge pull request !58 from AppleOfGray/N/A
2021-07-16 20:46:49 +00:00
省长 9f097b9c01 !59 update sa-token-doc/doc/use/kick.md.
Merge pull request !59 from autumn/N/A
2021-07-16 02:55:18 +00:00
autumn 9ddc85d876 update sa-token-doc/doc/use/kick.md. 2021-07-16 02:49:40 +00:00
AppleOfGray 3021f2c871 update sa-token-doc/doc/use/kick.md. 2021-07-16 02:46:33 +00:00
AppleOfGray ffb002dbb6 update sa-token-doc/doc/use/dao-extend.md. 2021-07-14 06:04:10 +00:00
省长 879894e5a7 !55 update sa-token-doc/doc/use/login-auth.md.
Merge pull request !55 from AppleOfGray/N/A
2021-07-13 08:32:25 +00:00
AppleOfGray 4e935cb054 update sa-token-doc/doc/use/login-auth.md. 2021-07-13 08:31:38 +00:00
click33 0290c010b1 v1.22.0 更新 2021-07-10 01:52:17 +08:00
click33 20ec0857cc v1.22.0 更新 2021-07-10 01:50:56 +08:00
click33 0bc8982212 quick-login插件新增拦截与放行路径配置 2021-07-09 20:41:35 +08:00
click33 936dfe333d 优化单点登录步骤 2021-07-09 02:31:34 +08:00
click33 0a5c5da4b4 v1.21.1 beta 2021-07-08 02:56:58 +08:00
click33 03d0f235d4 优化文档 2021-07-08 01:59:49 +08:00
click33 d333b07c58 完善SSO配置文档 2021-07-08 01:27:20 +08:00
click33 922e746eb1 简化单点登录集成步骤 2021-07-08 01:24:42 +08:00
click33 82f7d7f78c starter包架构调整 2021-07-04 22:31:28 +08:00
click33 12d76e34bb 添加在线测评 2021-07-04 21:41:23 +08:00
click33 b9df251aa3 优化Readme. 2021-07-02 21:43:15 +08:00
click33 4e166c1be4 优化Readme 2021-07-02 21:42:13 +08:00
click33 7d0037066a v1.21.0 更新 2021-07-02 17:33:31 +08:00
click33 b836e1f75a 优化算法 2021-07-02 15:30:43 +08:00
click33 40b82ce602 新增SaRouter.back()函数,用于停止匹配返回结果 2021-07-01 16:30:29 +08:00
click33 7b69b7915b 更改配置前缀 2021-07-01 15:15:54 +08:00
click33 d2e00b341d SSO 三种模式 2021-06-29 23:32:35 +08:00
click33 33318c6835 Merge pull request #95 from stepbystep2/patch-1
Update pom.xml
2021-06-29 16:02:45 +08:00
stepbystep2 7c4cb6f4d1 Update pom.xml
修复报错:java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter
2021-06-29 15:38:13 +08:00
click33 c6d5467db7 单点登录:模式二 2021-06-26 23:30:29 +08:00
click33 18757a23d2 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-06-24 18:01:16 +08:00
click33 dd2665ceb1 单点登录:CAS模式 2021-06-24 18:00:54 +08:00
省长 51e58b74fe !54 quick login 增加可配置include
Merge pull request !54 from addyu/xjy624
2021-06-24 05:38:04 +00:00
xiejiay 6935889d39 quick login 增加可配置include 2021-06-24 00:03:51 +08:00
click33 6c874e6737 Sa-Token-Alone-Redis 文档 2021-06-21 22:45:05 +08:00
click33 88c9c87de4 新增插件:Sa-Token独立Redis 2021-06-21 21:18:20 +08:00
click33 1ff29b47af 二级认证-文档 2021-06-19 23:08:14 +08:00
省长 16f67d16f1 update README.md. 2021-06-19 20:14:40 +08:00
click33 7695fbf05e 优化文档 2021-06-19 01:38:37 +08:00
click33 23c00b5d8e 二级认证的注解相关文档 2021-06-19 00:00:57 +08:00
click33 a73adf6727 优化注释 2021-06-18 23:53:16 +08:00
297 changed files with 12077 additions and 4765 deletions
+2
View File
@@ -12,3 +12,5 @@ unpackage/
/.factorypath
.idea/
+68 -36
View File
@@ -1,9 +1,9 @@
<p align="center">
<img alt="logo" src="https://gitee.com/dromara/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.20.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.24.0</h1>
<h4 align="center">这可能是史上功能最全的 Java 权限认证框架!</h4>
<h4 align="center">
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg"></a>
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.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>
@@ -11,7 +11,7 @@
<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>
<a href="https://github.com/dromara/sa-token/issues"><img src="https://img.shields.io/github/issues/dromara/sa-token.svg?style=flat-square&logo=GitHub"></a>
<a href="https://github.com/dromara/sa-token/blob/master/LICENSE"><img src="https://img.shields.io/github/license/dromara/sa-token.svg?style=flat-square"></a>
</h4>
</p>
---
@@ -20,47 +20,38 @@
- [在线文档:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- [调查问卷:Sa-Token 邀您填写满意度调查问卷](https://wj.qq.com/s2/8475114/2f6a/)
- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- [开源不易,求鼓励,点个star吧 ](###)
## Sa-Token是什么?
Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题
## Sa-Token 介绍
Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0、微服务网关鉴权 等一系列权限相关问题
框架针对踢人下线、自动续签、前后台分离、分布式会话……等常见业务进行N多适配,通过Sa-Token,你可以以一种极的方式实现系统的权限认证部分
框架集成简单、开箱即用、API设计清爽,通过Sa-Token,你以一种极其简单的方式实现系统的权限认证部分
与其它权限认证框架相比,`Sa-Token` 具有以下优势:
1. **简单** :可零配置启动框架,真正的开箱即用,低成本上手
2. **强大** :目前已集成几十项权限相关特性,涵盖了大部分业务场景的解决方案
3. **易用** :如丝般顺滑的API调用,大量高级特性统统只需一行代码即可实现
4. **高扩展** :几乎所有组件都提供了扩展接口,90%以上的逻辑都可以按需重写
有了Sa-Token,你所有的权限认证问题,都不再是问题!
## Sa-Token 能做什么?
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 适配RBAC权限模型,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
- **踢人下线** —— 将违规用户立刻清退下线
- **账号封禁** —— 封禁指定账号,使其无法登陆,还可指定解封时间
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
- **权限认证** —— 权限认证、角色认证、会话二级认证
- **Session会话** —— 全端共享Session、单端独享Session、自定义Session
- **踢人下线** —— 根据账号id踢人下线、根据Token值踢人下线
- **账号封禁** —— 指定天数封禁、永久封禁、设定解封时间
- **持久层扩展** —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
- **分布式会话** —— 提供jwt集成共享数据中心两种分布式会话方案
- **微服务网关鉴权** —— 适配Gateway、Soul、Zuul等常见网关组件的请求拦截认证
- **单点登录** —— 一处登录,处处通行
- **分布式会话** —— 提供jwt集成共享数据中心两种分布式会话方案
- **微服务网关鉴权** —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证
- **单点登录** —— 内置三种单点登录模式:无论是否跨域、是否共享Redis,都可以搞定
- **OAuth2.0认证** —— 基于RFC-6749标准编写,OAuth2.0标准流程的授权认证,支持openid模式
- **二级认证** —— 在已登录的基础上再次认证,保证安全性
- **独立Redis** —— 将权限缓存与业务缓存分离
- **临时Token验证** —— 解决短时间的Token授权问题
- **模拟他人账号** —— 实时操作任意用户状态数据
- **临时身份切换** —— 将会话身份临时切换为其它账号
- **无Cookie模式** —— APP、小程序等前后台分离场景
- **前后台分离** —— APP、小程序等不支持Cookie的终端
- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- **花式token生成** —— 内置六种token风格,还可自定义token生成策略
- **花式token生成** —— 内置六种Token风格,还可自定义Token生成策略、自定义Token前缀
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **路由拦截式鉴权** —— 根据路由拦截鉴权,可适配restful模式
- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签
- **自动续签** —— 提供两种Token过期策略,灵活搭配使用,还可自动续签
- **会话治理** —— 提供方便灵活的会话查询接口
- **记住我模式** —— 适配[记住我]模式,重启浏览器免验证
- **密码加密** —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
@@ -69,10 +60,46 @@ Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
##### Sa-Token 功能结构图
![sa-token-js](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/sa-token-js.png 's-w')
![sa-token-js](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-js3.png 's-w')
##### Sa-Token 认证流程图
![sa-token-rz](https://oss.dev33.cn/sa-token/doc/sa-token-rz.png 's-w')
![sa-token-rz](https://color-test.oss-cn-qingdao.aliyuncs.com/sa-token/x/sa-token-rz2.png 's-w')
## Sa-Token-SSO 单点登录
对于单点登录,网上教程大多以CAS模式为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) |
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com``c2.domain.com``c3.domain.com`
2. 后端同Redis:就是指多个系统可以连接同一个Redis,其它的缓存数据中心亦可。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成)
4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择
## Sa-Token-SSO 特性
1. API简单易用,文档介绍详细,且提供直接可用的集成示例
2. 支持三种模式,不论是否跨域、是否共享Redis,都可以完美解决
3. 安全性高:内置域名校验、Ticket校验、秘钥校验等,杜绝`Ticket劫持``Token窃取`等常见攻击手段(文档讲述攻击原理和防御手段)
4. 不丢参数:笔者曾试验多个单点登录框架,均有参数丢失的情况,比如重定向之前是:`http://a.com?id=1&name=2`,登录成功之后就变成了:`http://a.com?id=1`Sa-Token-SSO内有专门的算法保证了参数不丢失,登录成功之后原路返回页面
5. 无缝集成:由于Sa-Token本身就是一个权限认证框架,因此你可以只用一个框架同时解决`权限认证` + `单点登录`问题,让你不再到处搜索:xxx单点登录与xxx权限认证如何整合……
6. 高可定制:Sa-Token-SSO模块对代码架构侵入性极低,结合Sa-Token本身的路由拦截特性,你可以非常轻松的定制化开发
## Sa-Token-OAuth2.0 授权登录
Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) 编写,通过Sa-OAuth2你可以非常轻松的实现系统的OAuth2.0授权认证
1. 授权码(Authorization Code):OAuth2.0标准授权步骤,Server端向Client端下放Code码,Client端再用Code码换取授权Token
2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server端使用URL重定向方式直接将Token下放到Client端页面
3. 密码式(Password):Client直接拿着用户的账号密码换取授权Token
4. 客户端凭证(Client Credentials):Server端针对Client级别的Token,代表应用自身的资源授权
详细参考文档:[http://sa-token.dev33.cn/doc/index.html#/oauth2/readme](http://sa-token.dev33.cn/doc/index.html#/oauth2/readme)
## 代码示例
@@ -135,7 +162,7 @@ Sa-Token API 众多,请恕此处无法为您逐一展示,更多示例请戳
[![github-chart](https://starchart.cc/dromara/sa-token.svg 'GitHub')](https://starchart.cc/dromara/sa-token)
<!--
## 参与贡献
众人拾柴火焰高,万丈高楼众人起!
Sa-Token秉承着开放的思想,欢迎大家为框架添砖加瓦:
@@ -147,15 +174,18 @@ Sa-Token秉承着开放的思想,欢迎大家为框架添砖加瓦:
5. 其它部分:您可以参考项目issues与需求墙进行贡献
作者寄语:参与贡献不光只有提交代码,点一个star、提一个issues都是对开源项目的促进,
如果Sa-Token帮助到了你,欢迎你把框架推荐给朋友、同事使用,为Sa-Token的推广做一份贡献
如果Sa-Token帮助到了你,欢迎你把框架推荐给朋友、同事使用,为Sa-Token的推广做一份贡献
-->
## 使用Sa-Token的开源项目
[**[ sa-plus]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus)
[**[ sa-plus ]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus)
[**[ jthink]** 一个基于springboot+sa-token+thymeleaf的博客系统](https://gitee.com/wtsoftware/jthink)
[**[ jthink ]** 一个基于springboot+sa-token+thymeleaf的博客系统](https://gitee.com/wtsoftware/jthink)
[**[ dcy-fast]** 一个基于springboot+sa-token+mybatis-plus的后台管理系统,前端vue-element-admin,并且内置代码生成器](https://gitee.com/dcy421/dcy-fast)
[**[ dcy-fast ]** 一个基于springboot+sa-token+mybatis-plus的后台管理系统,前端vue-element-admin,并且内置代码生成器](https://gitee.com/dcy421/dcy-fast)
[**[ helio-starters ]** 基于JDK15 + Spring Boot 2.4 + Sa-Token + Mybatis-Plus的单体Boot版脚手架和微服务Cloud版脚手架,带有配套后台管理前端模板及代码生成器](https://gitee.com/uncarbon97/helio-starters)
如果您的项目使用了Sa-Token,欢迎提交pr
@@ -165,6 +195,8 @@ Sa-Token秉承着开放的思想,欢迎大家为框架添砖加瓦:
[**[ 小诺快速开发平台 ]** 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
[**[ Jpom ]** 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件](https://gitee.com/dromara/Jpom)
## 交流群
QQ交流群:1002350610 [点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
+29
View File
@@ -33,6 +33,35 @@ cd sa-token-demo-quick-login
call mvn clean
cd ..
cd sa-token-demo-alone-redis
call mvn clean
cd ..
cd sa-token-demo-sso1
call mvn clean
cd ..
cd sa-token-demo-sso2-server
call mvn clean
cd ..
cd sa-token-demo-sso2-client
call mvn clean
cd ..
cd sa-token-demo-sso3-server
call mvn clean
cd ..
cd sa-token-demo-sso3-client
call mvn clean
cd ..
cd ..
:: 最后打印
+2 -2
View File
@@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.20.0</version>
<version>1.24.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@@ -36,7 +36,7 @@
<!-- 一些属性 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
+2 -2
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.20.0</version>
<version>1.24.0</version>
</parent>
<packaging>jar</packaging>
@@ -16,7 +16,7 @@
<description>A Java Web lightweight authority authentication framework, comprehensive function, easy to use</description>
<dependencies>
<!-- Zero dependence -->
<!-- Zero dependence -->
</dependencies>
@@ -23,7 +23,7 @@ import cn.dev33.satoken.temp.SaTempDefaultImpl;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 管理sa-token所有接口对象
* 管理 Sa-Token 所有接口对象
* @author kong
*
*/
@@ -32,7 +32,7 @@ public class SaManager {
/**
* 配置文件 Bean
*/
private static SaTokenConfig config;
public static SaTokenConfig config;
public static void setConfig(SaTokenConfig config) {
SaManager.config = config;
if(config.getIsPrint()) {
@@ -6,7 +6,7 @@ import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token逻辑代理接口
* Sa-Token 逻辑代理接口
* <p>此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写</p>
* @author kong
*
@@ -14,22 +14,22 @@ import cn.dev33.satoken.session.SaSession;
public interface SaTokenAction {
/**
* 根据一定的算法生成一个token
* 创建一个Token
* @param loginId 账号id
* @param loginType 账号类型
* @return 一个token
* @return token
*/
public String createToken(Object loginId, String loginType);
/**
* 根据 SessionId 创建一个 Session
* 创建一个Session
* @param sessionId Session的Id
* @return 创建后的Session
*/
public SaSession createSession(String sessionId);
/**
* 指定集合是否包含指定元素(模糊匹配)
* 判断:集合是否包含指定元素(模糊匹配)
* @param list 集合
* @param element 元素
* @return 是否包含
@@ -9,19 +9,20 @@ import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* SaTokenAction 接口的默认实现
* Sa-Token 逻辑代理接口 [默认实现类]
* @author kong
*
*/
public class SaTokenActionDefaultImpl implements SaTokenAction {
/**
* 根据一定的算法生成一个token
* 创建一个Token
*/
@Override
public String createToken(Object loginId, String loginType) {
@@ -56,7 +57,7 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
}
/**
* 根据 SessionId 创建一个 Session
* 创建一个Session
*/
@Override
public SaSession createSession(String sessionId) {
@@ -64,11 +65,12 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
}
/**
* 指定集合是否包含指定元素(模糊匹配)
* 判断:集合是否包含指定元素(模糊匹配)
*/
@Override
public boolean hasElement(List<String> list, String element) {
// 集合为空直接返回false
// 空集合直接返回false
if(list == null || list.size() == 0) {
return false;
}
@@ -90,7 +92,7 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
}
/**
* 对一个Method对象进行注解权限校验(注解鉴权逻辑内部实现)
* 对一个Method对象进行注解检查(注解鉴权内部实现)
*/
@Override
public void checkMethodAnnotation(Method method) {
@@ -125,6 +127,12 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
SaCheckPermission at = target.getAnnotation(SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
// 校验 @SaCheckSafe 注解
if(target.isAnnotationPresent(SaCheckSafe.class)) {
SaCheckSafe at = target.getAnnotation(SaCheckSafe.class);
SaManager.getStpLogic(null).checkByAnnotation(at);
}
}
}
@@ -6,8 +6,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录校验:标注在一个方法上,当前会话必须已经登录才能进入该方法
* <p> 可标注在类上,其效果等同于标注在此类的所有方法上
* 登录认证:只有登录之后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上
* @author kong
*
*/
@@ -6,8 +6,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限校验:标注在一个方法上,当前会话必须具有指定权限才能进入该方法
* <p> 可标注在类上,其效果等同于标注在此类的所有方法上
* 权限认证:必须具有指定权限才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上
* @author kong
*
*/
@@ -6,8 +6,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色校验:标注在一个方法上,当前会话必须具有指定角色标识才能进入该方法
* <p> 可标注在类上,其效果等同于标注在此类的所有方法上
* 角色认证:必须具有指定角色标识才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上
* @author kong
*
*/
@@ -0,0 +1,18 @@
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 kong
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaCheckSafe {
}
@@ -0,0 +1,296 @@
package cn.dev33.satoken.config;
import java.io.Serializable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO 单点登录模块 配置Model
* @author kong
*
*/
public class SaSsoConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* Ticket有效期 (单位: 秒)
*/
public long ticketTimeout = 60 * 5;
/**
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String allowUrl = "*";
/**
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
*/
public String secretkey;
/**
* SSO-Server端 单点登录地址
*/
public String authUrl;
/**
* SSO-Server端 Ticket校验地址
*/
public String checkTicketUrl;
/**
* SSO-Server端 单点注销地址
*/
public String sloUrl;
/**
* SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
*/
public String ssoLogoutCall;
/**
* @return Ticket有效期 (单位: 秒)
*/
public long getTicketTimeout() {
return ticketTimeout;
}
/**
* @param ticketTimeout Ticket有效期 (单位: 秒)
* @return 对象自身
*/
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
this.ticketTimeout = ticketTimeout;
return this;
}
/**
* @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String getAllowUrl() {
return allowUrl;
}
/**
* @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return 对象自身
*/
public SaSsoConfig setAllowUrl(String allowUrl) {
this.allowUrl = allowUrl;
return this;
}
/**
* @return 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
*/
public String getSecretkey() {
return secretkey;
}
/**
* @param secretkey 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
* @return 对象自身
*/
public SaSsoConfig setSecretkey(String secretkey) {
this.secretkey = secretkey;
return this;
}
/**
* @return SSO-Server端 单点登录地址
*/
public String getAuthUrl() {
return authUrl;
}
/**
* @param authUrl SSO-Server端 单点登录地址
* @return 对象自身
*/
public SaSsoConfig setAuthUrl(String authUrl) {
this.authUrl = authUrl;
return this;
}
/**
* @return SSO-Server端Ticket校验地址
*/
public String getCheckTicketUrl() {
return checkTicketUrl;
}
/**
* @param checkTicketUrl SSO-Server端Ticket校验地址
*/
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
this.checkTicketUrl = checkTicketUrl;
return this;
}
/**
* @return SSO-Server端单点注销地址
*/
public String getSloUrl() {
return sloUrl;
}
/**
* @param sloUrl SSO-Server端单点注销地址
* @return 对象自身
*/
public SaSsoConfig setSloUrl(String sloUrl) {
this.sloUrl = sloUrl;
return this;
}
/**
* @return SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
*/
public String getSsoLogoutCall() {
return ssoLogoutCall;
}
/**
* @param ssoLogoutCall SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
* @return 对象自身
*/
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
this.ssoLogoutCall = ssoLogoutCall;
return this;
}
@Override
public String toString() {
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", secretkey=" + secretkey
+ ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", sloUrl=" + sloUrl
+ ", ssoLogoutCall=" + ssoLogoutCall + ", isHttp=" + isHttp + ", isSlo=" + isSlo + "]";
}
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* @return 对象自身
*/
public SaSsoConfig setAllow(String ...url) {
this.allowUrl = SaFoxUtil.arrayJoin(url);
return this;
}
// -------------------- SaSsoHandle 相关配置 --------------------
/**
* 是否使用http请求校验ticket值
*/
public Boolean isHttp = false;
/**
* 是否打开单点注销
*/
public Boolean isSlo = false;
/**
* @return isHttp 是否使用http请求校验ticket值
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否使用http请求校验ticket值
* @return 对象自身
*/
public SaSsoConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* @return 是否打开单点注销
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销
* @return 对象自身
*/
public SaSsoConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
// -------------------- SaSsoHandle 所有回调函数 --------------------
/**
* SSO-Server端:未登录时返回的View
*/
public Supplier<Object> notLoginView = () -> "当前会话在SSO-Server认证中心尚未登录";
/**
* SSO-Server端:登录函数
*/
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
/**
* SSO-Client端:Ticket无效时返回的View
*/
public Function<String, Object> ticketInvalidView = (ticket) -> {
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
return "ticket无效: " + ticket;
};
/**
* SSO-Client端:发送Http请求的处理函数
*/
public Function<String, Object> sendHttp = url -> {throw new SaTokenException("请配置Http处理器");};
/**
* @param notLoginView SSO-Server端:未登录时返回的View
* @return 对象自身
*/
public SaSsoConfig setNotLoginView(Supplier<Object> notLoginView) {
this.notLoginView = notLoginView;
return this;
}
/**
* @param doLoginHandle SSO-Server端:登录函数
* @return 对象自身
*/
public SaSsoConfig setDoLoginHandle(BiFunction<String, String, Object> doLoginHandle) {
this.doLoginHandle = doLoginHandle;
return this;
}
/**
* @param ticketInvalidView SSO-Client端:Ticket无效时返回的View
* @return 对象自身
*/
public SaSsoConfig setTicketInvalidView(Function<String, Object> ticketInvalidView) {
this.ticketInvalidView = ticketInvalidView;
return this;
}
/**
* @param sendHttp SSO-Client端:发送Http请求的处理函数
* @return 对象自身
*/
public SaSsoConfig setSendHttp(Function<String, Object> sendHttp) {
this.sendHttp = sendHttp;
return this;
}
}
@@ -1,20 +1,24 @@
package cn.dev33.satoken.config;
import java.io.Serializable;
/**
* sa-token 配置类 Model
* Sa-Token 配置类 Model
* <p>
* 你可以通过yml、properties、java代码等形式配置本类参数,具体请查阅官方文档: http://sa-token.dev33.cn/
*
* @author kong
*
*/
public class SaTokenConfig {
public class SaTokenConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/** token名称 (同时也是cookie名称) */
private String tokenName = "satoken";
/** token的长久有效期(单位:秒) 默认30天, -1代表永久 */
private long timeout = 30 * 24 * 60 * 60;
private long timeout = 60 * 60 * 24 * 30;
/**
* token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
@@ -66,6 +70,17 @@ public class SaTokenConfig {
*/
private String jwtSecretKey;
/**
* Id-Token的有效期 (单位: 秒)
*/
private long idTokenTimeout = 60 * 60 * 24;
/**
* SSO单点登录配置对象
*/
public SaSsoConfig sso = new SaSsoConfig();
/**
* @return token名称 (同时也是cookie名称)
@@ -344,20 +359,49 @@ public class SaTokenConfig {
}
/**
* toString()
* @return Id-Token的有效期 (单位: 秒)
*/
public long getIdTokenTimeout() {
return idTokenTimeout;
}
/**
* @param idTokenTimeout Id-Token的有效期 (单位: 秒)
* @return 对象自身
*/
public SaTokenConfig setIdTokenTimeout(long idTokenTimeout) {
this.idTokenTimeout = idTokenTimeout;
return this;
}
/**
* @return SSO单点登录配置对象
*/
public SaSsoConfig getSso() {
return sso;
}
/**
* @param sso SSO单点登录配置对象
*/
public void setSso(SaSsoConfig sso) {
this.sso = sso;
}
@Override
public String toString() {
return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout
+ ", isConcurrent=" + isConcurrent + ", isShare=" + isShare + ", isReadBody="
+ isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle="
+ tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin="
+ tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain
+ ", tokenPrefix=" + tokenPrefix + ", isPrint=" + isPrint + ", isLog=" + isLog + ", jwtSecretKey="
+ jwtSecretKey + "]";
+ ", isConcurrent=" + isConcurrent + ", isShare=" + isShare + ", isReadBody=" + isReadBody
+ ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle
+ ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin=" + tokenSessionCheckLogin
+ ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain + ", tokenPrefix=" + tokenPrefix
+ ", isPrint=" + isPrint + ", isLog=" + isLog + ", jwtSecretKey=" + jwtSecretKey + ", idTokenTimeout="
+ idTokenTimeout + ", sso=" + sso + "]";
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
* @param allowConcurrentLogin see note
@@ -369,7 +413,6 @@ public class SaTokenConfig {
return this;
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
* @param isV see note
@@ -8,7 +8,7 @@ import java.util.Map;
import java.util.Properties;
/**
* sa-token配置文件的构建工厂类
* Sa-Token配置文件的构建工厂类
* <p>
* 只有在非IOC环境下才会用到此类
*
@@ -5,7 +5,7 @@ import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
/**
* 上下文环境 [ThreadLocal版本]
* Sa-Token 上下文处理器 [ThreadLocal版本]
* @author kong
*
*/
@@ -6,19 +6,19 @@ import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.exception.SaTokenException;
/**
* 基于ThreadLocal的上下文对象存储器
* Sa-Token 上下文处理器 [ThreadLocal版本] ---- 对象存储器
* @author kong
*
*/
public class SaTokenContextForThreadLocalStorage {
/**
* 基于 ThreadLocal 的上下文
* 基于 ThreadLocal 的 [Box存储器]
*/
static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<Box>();
public static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<Box>();
/**
* 初始化 [器]
* 初始化 [Box存储器]
* @param request {@link SaRequest}
* @param response {@link SaResponse}
* @param storage {@link SaStorage}
@@ -29,14 +29,14 @@ public class SaTokenContextForThreadLocalStorage {
};
/**
* 清除 [器]
* 清除 [Box存储器]
*/
public static void clearBox() {
boxThreadLocal.remove();
};
/**
* 获取 [器]
* 获取 [Box存储器]
* @return see note
*/
public static Box getBox() {
@@ -44,7 +44,7 @@ public class SaTokenContextForThreadLocalStorage {
};
/**
* 获取 [器], 如果为空则抛出异常
* 获取 [Box存储器], 如果为空则抛出异常
* @return see note
*/
public static Box getBoxNotNull() {
@@ -56,7 +56,7 @@ public class SaTokenContextForThreadLocalStorage {
};
/**
* 在 [上下文容器] 获取 [Request] 对象
* 在 [Box存储器] 获取 [Request] 对象
*
* @return see note
*/
@@ -65,7 +65,7 @@ public class SaTokenContextForThreadLocalStorage {
}
/**
* 在 [上下文容器] 获取 [Response] 对象
* 在 [Box存储器] 获取 [Response] 对象
*
* @return see note
*/
@@ -74,7 +74,7 @@ public class SaTokenContextForThreadLocalStorage {
}
/**
* 在 [上下文容器] 获取 [存储器] 对象
* 在 [Box存储器] 获取 [存储器] 对象
*
* @return see note
*/
@@ -84,7 +84,7 @@ public class SaTokenContextForThreadLocalStorage {
/**
* 临时内部类,存储三个对象
* 临时内部类,用于存储[request、response、storage]三个对象
* @author kong
*/
/**
@@ -1,10 +0,0 @@
package cn.dev33.satoken.context.model;
/**
* Cookie 包装类
* @author kong
*
*/
public class SaCookie {
}
@@ -1,7 +1,10 @@
package cn.dev33.satoken.context.model;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Request包装类
* Request 包装类
* @author kong
*
*/
@@ -18,8 +21,47 @@ public interface SaRequest {
* @param name 键
* @return 值
*/
public String getParameter(String name);
public String getParam(String name);
/**
* 在 [请求体] 里获取一个值,值为空时返回默认值
* @param name 键
* @param defaultValue 值为空时的默认值
* @return 值
*/
public default String getParam(String name, String defaultValue) {
String value = getParam(name);
if(SaFoxUtil.isEmpty(value)) {
return defaultValue;
}
return value;
}
/**
* 检测提供的参数是否为指定值
* @param name 键
* @param value 值
* @return 是否相等
*/
public default boolean isParam(String name, String value) {
String paramValue = getParam(name);
return paramValue != null && paramValue.equals(value);
}
/**
* 在 [请求体] 里获取一个值 (此值必须存在,否则抛出异常 )
* @param name 键
* @return 参数值
*/
public default String getParamNotNull(String name) {
String paramValue = getParam(name);
if(SaFoxUtil.isEmpty(paramValue)) {
throw new SaTokenException("缺少参数:" + name);
}
return paramValue;
}
/**
* 在 [请求头] 里获取一个值
* @param name 键
@@ -40,10 +82,33 @@ public interface SaRequest {
*/
public String getRequestPath();
/**
* 返回当前请求path是否为指定值
* @param path path
* @return see note
*/
public default boolean isPath(String path) {
return getRequestPath().equals(path);
}
/**
* 返回当前请求的url,例:http://xxx.com/
* @return see note
*/
public String getUrl();
/**
* 返回当前请求的类型
* @return see note
*/
public String getMethod();
/**
* 此请求是否为Ajax请求
* @return see note
*/
public default boolean isAjax() {
return getHeader("X-Requested-With") != null;
}
}
@@ -46,4 +46,11 @@ public interface SaResponse {
return this.setHeader("Server", value);
}
/**
* 重定向
* @param url 重定向地址
* @return 任意值
*/
public Object redirect(String url);
}
@@ -5,7 +5,7 @@ import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层接口
* Sa-Token持久层接口
* @author kong
*/
public interface SaTokenDao {
@@ -17,101 +17,101 @@ public interface SaTokenDao {
public static final long NOT_VALUE_EXPIRE = -2;
// --------------------- token相关 ---------------------
// --------------------- 字符串读写 ---------------------
/**
* 根据key获取value,如果没有,则返回
* 获取Value,如无返
* @param key 键名称
* @return value
*/
public String get(String key);
/**
* 写入指定key-value键值对,并设定过期时间 (单位: 秒)
* 写入Value,并设定存活时间 (单位: 秒)
* @param key 键名称
* @param value 值
* @param timeout 过期时间 (单位: 秒)
* @param timeout 过期时间(值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
*/
public void set(String key, String value, long timeout);
/**
* 修改指定key-value键值对 (过期时间不变)
* 更新Value (过期时间不变)
* @param key 键名称
* @param value 值
*/
public void update(String key, String value);
/**
* 删除一个指定的key
* 删除Value
* @param key 键名称
*/
public void delete(String key);
/**
* 获取指定key的剩余存活时间 (单位: 秒)
* 获取Value的剩余存活时间 (单位: 秒)
* @param key 指定key
* @return 这个key的剩余存活时间
*/
public long getTimeout(String key);
/**
* 修改指定key的剩余存活时间 (单位: 秒)
* 修改Value的剩余存活时间 (单位: 秒)
* @param key 指定key
* @param timeout 过期时间
*/
public void updateTimeout(String key, long timeout);
// --------------------- Object相关 ---------------------
// --------------------- 对象读写 ---------------------
/**
* 根据key获取Object,如果没有,则返回
* 获取Object,如无返
* @param key 键名称
* @return object
*/
public Object getObject(String key);
/**
* 写入指定键值对,并设定过期时间 (单位: 秒)
* 写入Object,并设定存活时间 (单位: 秒)
* @param key 键名称
* @param object 值
* @param timeout 过期时间 (单位: 秒)
* @param timeout 存活时间 (值大于0时限时存储,值=-1时永久存储,值=0或小于-2时不存储)
*/
public void setObject(String key, Object object, long timeout);
/**
* 修改指定键值对 (过期时间不变)
* 更新Object (过期时间不变)
* @param key 键名称
* @param object 值
*/
public void updateObject(String key, Object object);
/**
* 删除一个指定的Object
* 删除Object
* @param key 键名称
*/
public void deleteObject(String key);
/**
* 获取指定key的剩余存活时间 (单位: 秒)
* 获取Object的剩余存活时间 (单位: 秒)
* @param key 指定key
* @return 这个key的剩余存活时间
*/
public long getObjectTimeout(String key);
/**
* 修改指定key的剩余存活时间 (单位: 秒)
* 修改Object的剩余存活时间 (单位: 秒)
* @param key 指定key
* @param timeout 过期时间
*/
public void updateObjectTimeout(String key, long timeout);
// --------------------- Session相关 ---------------------
// --------------------- Session读写 ---------------------
/**
* 根据指定key的Session,如果没有,则返回
* @param sessionId 键名称
* 获取Session,如无返
* @param sessionId sessionId
* @return SaSession
*/
public default SaSession getSession(String sessionId) {
@@ -119,8 +119,8 @@ public interface SaTokenDao {
}
/**
* 将指定Session持久化
* @param session 要保存的session对象
* 写入Session,并设定存活时间 (单位: 秒)
* @param session 要保存的Session对象
* @param timeout 过期时间 (单位: 秒)
*/
public default void setSession(SaSession session, long timeout) {
@@ -128,7 +128,7 @@ public interface SaTokenDao {
}
/**
* 更新指定session
* 更新Session
* @param session 要更新的session对象
*/
public default void updateSession(SaSession session) {
@@ -136,7 +136,7 @@ public interface SaTokenDao {
}
/**
* 删除一个指定的session
* 删除Session
* @param sessionId sessionId
*/
public default void deleteSession(String sessionId) {
@@ -144,17 +144,17 @@ public interface SaTokenDao {
}
/**
* 获取指定SaSession剩余存活时间 (单位: 秒)
* @param sessionId 指定SaSession
* @return 这个SaSession的剩余存活时间 (单位: 秒)
* 获取Session剩余存活时间 (单位: 秒)
* @param sessionId 指定Session
* @return 这个Session的剩余存活时间
*/
public default long getSessionTimeout(String sessionId) {
return getObjectTimeout(sessionId);
}
/**
* 修改指定SaSession剩余存活时间 (单位: 秒)
* @param sessionId sessionId
* 修改Session剩余存活时间 (单位: 秒)
* @param sessionId 指定Session
* @param timeout 过期时间
*/
public default void updateSessionTimeout(String sessionId, long timeout) {
@@ -10,7 +10,7 @@ import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* sa-token持久层默认实现类 , 基于内存Map
* Sa-Token持久层接口 [默认实现类, 基于内存Map]
* @author kong
*
*/
@@ -45,6 +45,9 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
@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));
}
@@ -84,6 +87,9 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
@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));
}
@@ -0,0 +1,29 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表停止匹配,直接退出,向前端输出结果
*
* @author kong
*/
public class BackResultException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130143L;
/**
* 要输出的结果
*/
public Object result;
/**
* 构造
* @param result 要输出的结果
*/
public BackResultException(Object result) {
super(String.valueOf(result));
this.result = result;
}
}
@@ -60,7 +60,7 @@ public class DisableLoginException extends SaTokenException {
}
/**
* 构造方法创建一个
* 一个异常:代表账号已被封禁
*
* @param loginType 账号类型
* @param loginId 被封禁的账号id
@@ -73,6 +73,4 @@ public class DisableLoginException extends SaTokenException {
this.disableTime = disableTime;
}
}
@@ -0,0 +1,22 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表提供的 Id-Token 无效
*
* @author kong
*/
public class IdTokenInvalidException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/**
* 一个异常:代表提供的 Id-Token 无效
*/
public IdTokenInvalidException(String message) {
super(message);
}
}
@@ -4,7 +4,7 @@ import java.util.Arrays;
import java.util.List;
/**
* 一个异常:代表用户没有登录
* 一个异常:代表会话未能通过登录认证
* @author kong
*/
public class NotLoginException extends SaTokenException {
@@ -3,7 +3,7 @@ package cn.dev33.satoken.exception;
import cn.dev33.satoken.stp.StpUtil;
/**
* 没有指定权限码,抛出的异常
* 一个异常:代表会话未能通过权限认证
*
* @author kong
*
@@ -3,7 +3,7 @@ package cn.dev33.satoken.exception;
import cn.dev33.satoken.stp.StpUtil;
/**
* 没有指定角色标识,抛出的异常
* 一个异常:代表会话未能通过角色认证
*
* @author kong
*
@@ -0,0 +1,25 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表会话未能通过二级认证
*
* @author kong
*/
public class NotSafeException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/** 异常提示语 */
public static final String BE_MESSAGE = "二级认证失败";
/**
* 一个异常:代表会话未通过二级认证
*/
public NotSafeException() {
super(BE_MESSAGE);
}
}
@@ -1,8 +1,8 @@
package cn.dev33.satoken.exception;
/**
* sa-token框架内部逻辑发生错误抛出的异常
* (自定义此异常方便开发者在做全局异常处理时分辨异常类型)
* Sa-Token框架内部逻辑发生错误抛出的异常
* (自定义此异常方便开发者在做全局异常处理时分辨异常类型)
*
* @author kong
*
@@ -1,7 +1,7 @@
package cn.dev33.satoken.filter;
/**
* sa-token全局过滤器-认证策略
* Sa-Token全局过滤器-认证策略
* @author kong
*
*/
@@ -1,7 +1,7 @@
package cn.dev33.satoken.filter;
/**
* sa-token全局过滤器-异常处理策略
* Sa-Token全局过滤器-异常处理策略
* @author kong
*
*/
@@ -1,7 +1,7 @@
package cn.dev33.satoken.fun;
/**
* 根据boolean变量,决定是否执行一个函数
* 根据Boolean变量,决定是否执行一个函数
*
* @author kong
*
@@ -5,14 +5,13 @@ package cn.dev33.satoken.fun;
* @author kong
*
*/
@FunctionalInterface
public interface SaRetFunction {
/**
* 执行的方法
* @return 返回值
*/
public Object run();
}
@@ -0,0 +1,172 @@
package cn.dev33.satoken.id;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.IdTokenInvalidException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Sa-Token-Id 身份凭证模块
* <p> 身份凭证的获取与校验,可用于微服务内部调用鉴权
* @author kong
*
*/
public class SaIdTemplate {
/**
* 在 Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = "SA_ID_TOKEN";
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在,则立即创建并返回
* @return 当前token
*/
public String getToken() {
String currentToken = getTokenNh();
if(SaFoxUtil.isEmpty(currentToken)) {
// 注意这里的自刷新不能做到高并发可用
currentToken = refreshToken();
}
return currentToken;
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public boolean isValid(String token) {
// 1、 如果传入的token未空,立即返回false
if(SaFoxUtil.isEmpty(token)) {
return false;
}
// 2、 验证当前 Id-Token 及 Past-Id-Token
return token.equals(getToken()) || token.equals(getPastTokenNh());
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public void checkToken(String token) {
if(isValid(token) == false) {
token = (token == null ? "" : token);
throw new IdTokenInvalidException("无效Id-Token" + token);
}
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public void checkCurrentRequestToken() {
checkToken(SaHolder.getRequest().getHeader(ID_TOKEN));
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public String refreshToken() {
// 1. 先将当前 Id-Token 写入到 Past-Id-Token 中
String idToken = getTokenNh();
if(SaFoxUtil.isEmpty(idToken) == false) {
savePastToken(idToken, getTokenTimeout());
}
// 2. 再刷新当前Id-Token
String newIdToken = createToken();
saveToken(newIdToken);
// 3. 返回新的 Id-Token
return newIdToken;
}
// ------------------------------ 保存Token
/**
* 保存Id-Token
* @param token
*/
public void saveToken(String token) {
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingTokenSaveKey(), token, SaManager.getConfig().getIdTokenTimeout());
}
/**
* 保存Past-Id-Token
* @param token token
* @param timeout 有效期(单位:秒)
*/
public void savePastToken(String token, long timeout){
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingPastTokenSaveKey(), token, timeout);
}
// -------------------- 获取Token
/**
* 获取Id-Token,不做任何处理
* @return token
*/
public String getTokenNh() {
return SaManager.getSaTokenDao().get(splicingTokenSaveKey());
}
/**
* 获取Past-Id-Token,不做任何处理
* @return token
*/
public String getPastTokenNh() {
return SaManager.getSaTokenDao().get(splicingPastTokenSaveKey());
}
/**
* 获取Id-Token的剩余有效期 (单位:秒)
* @return token
*/
public long getTokenTimeout() {
return SaManager.getSaTokenDao().getTimeout(splicingTokenSaveKey());
}
// -------------------- 创建Token
/**
* 创建一个Id-Token
* @return Token
*/
public String createToken() {
return SaFoxUtil.getRandomString(60);
}
// -------------------- 拼接key
/**
* 拼接keyId-Token的存储key
* @return key
*/
public String splicingTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:id-token";
}
/**
* 拼接keyId-Token的存储key
* @return key
*/
public String splicingPastTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:past-id-token";
}
}
@@ -0,0 +1,81 @@
package cn.dev33.satoken.id;
/**
* Sa-Token-Id 身份凭证模块-工具类
* @author kong
*
*/
public class SaIdUtil {
/**
* 在 Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = SaIdTemplate.ID_TOKEN;
/**
* 底层 SaIdTemplate 对象
*/
public static SaIdTemplate saIdTemplate = new SaIdTemplate();
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在,则立即创建并返回
* @return 当前token
*/
public static String getToken() {
return saIdTemplate.getToken();
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public static boolean isValid(String token) {
return saIdTemplate.isValid(token);
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public static void checkToken(String token) {
saIdTemplate.checkToken(token);
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public static void checkCurrentRequestToken() {
saIdTemplate.checkCurrentRequestToken();
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public static String refreshToken() {
return saIdTemplate.refreshToken();
}
// -------------------- 获取Token
/**
* 获取Id-Token,不做任何处理
* @return token
*/
public static String getTokenNh() {
return saIdTemplate.getTokenNh();
}
/**
* 获取Past-Id-Token,不做任何处理
* @return token
*/
public static String getPastTokenNh() {
return saIdTemplate.getPastTokenNh();
}
}
@@ -3,7 +3,7 @@ package cn.dev33.satoken.listener;
import cn.dev33.satoken.stp.SaLoginModel;
/**
* Sa-Token侦听器
* Sa-Token 侦听器
* <p> 你可以通过实现此接口在用户登陆、退出等关键性操作时进行一些AOP操作
* @author kong
*
@@ -4,7 +4,7 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
/**
* 执行验证方法的辅助类
* 路由拦截器验证方法Lambda
*
* @author kong
*
@@ -5,12 +5,13 @@ 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.IsRunFunction;
import cn.dev33.satoken.fun.SaFunction;
/**
* 路由匹配符相关操作的封装工具类
* 路由匹配操作工具类
* @author kong
*
*/
@@ -19,9 +20,9 @@ public class SaRouter {
// -------------------- 路由匹配相关 --------------------
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
* 路由匹配
* @param pattern 路由匹配符
* @param path 需要匹配的路
* @param path 匹配的路
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
@@ -29,9 +30,9 @@ public class SaRouter {
}
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
* @param patterns 路由匹配符
* @param path 需要匹配的路径集合
* 路由匹配
* @param patterns 路由匹配符集合
* @param path 匹配的路
* @return 是否匹配成功
*/
public static boolean isMatch(List<String> patterns, String path) {
@@ -44,7 +45,7 @@ public class SaRouter {
}
/**
* 校验指定路由匹配符是否可以匹配成功当前URI
* 路由匹配 (使用当前URI)
* @param pattern 路由匹配符
* @return 是否匹配成功
*/
@@ -53,8 +54,8 @@ public class SaRouter {
}
/**
* 校验指定路由匹配符是否可以匹配成功当前URI
* @param patterns 路由匹配符
* 路由匹配 (使用当前URI)
* @param patterns 路由匹配符集合
* @return 是否匹配成功
*/
public static boolean isMatchCurrURI(List<String> patterns) {
@@ -65,7 +66,7 @@ public class SaRouter {
// -------------------- 执行相关 --------------------
/**
* 使用路由匹配符与当前URI执行匹配,如果匹配成功则执行证函数
* 路由匹配,如果匹配成功则执行证函数
* @param pattern 路由匹配符
* @param function 要执行的方法
*/
@@ -76,7 +77,7 @@ public class SaRouter {
}
/**
* 使用路由匹配符与当前URI执行匹配 (并指定排除匹配符),如果匹配成功则执行证函数
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行证函数
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param function 要执行的方法
@@ -90,7 +91,7 @@ public class SaRouter {
}
/**
* 使用路由匹配符集合与当前URI执行匹配,如果匹配成功则执行证函数
* 路由匹配,如果匹配成功则执行证函数
* @param patterns 路由匹配符集合
* @param function 要执行的方法
*/
@@ -101,7 +102,7 @@ public class SaRouter {
}
/**
* 使用路由匹配符集合与当前URI执行匹配 (并指定排除匹配符),如果匹配成功则执行证函数
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行证函数
* @param patterns 路由匹配符集合
* @param excludePatterns 要排除的路由匹配符集合
* @param function 要执行的方法
@@ -116,7 +117,7 @@ public class SaRouter {
/**
* 使用路由匹配符集合与当前URI执行匹配,如果匹配成功则执行证函数
* 路由匹配,如果匹配成功则执行证函数
* @param patterns 路由匹配符集合
* @return 匹配结果包装对象
*/
@@ -134,8 +135,20 @@ public class SaRouter {
public static void stop() {
throw new StopMatchException();
}
/**
* 停止匹配,结束执行,向前端返回结果
* @param result 要输出的结果
*/
public static void back(Object result) {
throw new BackResultException(result);
}
/**
* 停止匹配,结束执行,向前端返回结果
*/
public static void back() {
throw new BackResultException("");
}
}
@@ -4,7 +4,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Base64;
/**
* Base64工具类
* Sa-Token Base64工具类
* @author kong
*
*/
@@ -57,5 +57,4 @@ public class SaBase64Util {
}
}
}
@@ -26,7 +26,7 @@ import javax.crypto.spec.SecretKeySpec;
import cn.dev33.satoken.exception.SaTokenException;
/**
* sa-token 常见加密算法工具类
* Sa-Token 常见加密算法工具类
*
* @author kong
*
@@ -36,7 +36,11 @@ public class SaSession implements Serializable {
* 构建一个Session对象
*/
public SaSession() {
this(null);
/*
* 当Session从Redis中反序列化取出时,框架会误以为创建了新的Session,
* 因此此处不可以调用this(null); 避免监听器收到错误的通知
*/
// this(null);
}
/**
@@ -147,100 +151,6 @@ public class SaSession implements Serializable {
}
}
// ----------------------- 存取值
/**
* 写入一个值
*
* @param key 名称
* @param value 值
*/
public void setAttribute(String key, Object value) {
dataMap.put(key, value);
update();
}
/**
* 取出一个值
*
* @param key 名称
* @return 值
*/
public Object getAttribute(String key) {
return dataMap.get(key);
}
/**
* 取值,并指定取不到值时的默认值
*
* @param key 名称
* @param defaultValue 取不到值的时候返回的默认值
* @return value
*/
public Object getAttribute(String key, Object defaultValue) {
Object value = getAttribute(key);
if (value != null) {
return value;
}
return defaultValue;
}
/**
* 移除一个值
*
* @param key 要移除的值的名字
*/
public void removeAttribute(String key) {
dataMap.remove(key);
update();
}
/**
* 清空所有值
*/
public void clearAttribute() {
dataMap.clear();
update();
}
/**
* 是否含有指定key
*
* @param key 是否含有指定值
* @return 是否含有
*/
public boolean containsAttribute(String key) {
return dataMap.containsKey(key);
}
/**
* 返回当前session会话所有key
*
* @return 所有值的key列表
*/
public Set<String> attributeKeys() {
return dataMap.keySet();
}
/**
* 获取数据挂载集合(如果更新map里的值,请调用session.update()方法避免产生脏数据
*
* @return 返回底层储存值的map对象
*/
public Map<String, Object> getDataMap() {
return dataMap;
}
/**
* 写入数据集合 (不改变底层对象,只将此dataMap所有数据进行替换)
* @param dataMap 数据集合
*/
public void refreshDataMap(Map<String, Object> dataMap) {
this.dataMap.clear();
this.dataMap.putAll(dataMap);
this.update();
}
// ----------------------- 一些操作
@@ -422,6 +332,15 @@ public class SaSession implements Serializable {
return SaFoxUtil.getValueByType(value, cs);
}
/**
* 返回当前Session的所有key
*
* @return 所有值的key列表
*/
public Set<String> keys() {
return dataMap.keySet();
}
// ---- 其他
/**
* 写值
@@ -468,7 +387,34 @@ public class SaSession implements Serializable {
update();
return this;
}
/**
* 清空所有值
*/
public void clear() {
dataMap.clear();
update();
}
/**
* 获取数据挂载集合(如果更新map里的值,请调用session.update()方法避免产生脏数据
*
* @return 返回底层储存值的map对象
*/
public Map<String, Object> getDataMap() {
return dataMap;
}
/**
* 写入数据集合 (不改变底层对象,只将此dataMap所有数据进行替换)
* @param dataMap 数据集合
*/
public void refreshDataMap(Map<String, Object> dataMap) {
this.dataMap.clear();
this.dataMap.putAll(dataMap);
this.update();
}
// --------- 工具方法
@@ -503,4 +449,96 @@ public class SaSession implements Serializable {
// ----------------------- 旧API
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.set(key) </h1>
* 写入一个值
*
* @param key 名称
* @param value 值
*/
@Deprecated
public void setAttribute(String key, Object value) {
dataMap.put(key, value);
update();
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.get(key) </h1>
* 取出一个值
*
* @param key 名称
* @return 值
*/
@Deprecated
public Object getAttribute(String key) {
return dataMap.get(key);
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.get(key, defaultValue) </h1>
* 取值,并指定取不到值时的默认值
*
* @param key 名称
* @param defaultValue 取不到值的时候返回的默认值
* @return value
*/
@Deprecated
public Object getAttribute(String key, Object defaultValue) {
Object value = getAttribute(key);
if (value != null) {
return value;
}
return defaultValue;
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.delete(key) </h1>
* 移除一个值
*
* @param key 要移除的值的名字
*/
@Deprecated
public void removeAttribute(String key) {
dataMap.remove(key);
update();
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.clear() </h1>
* 清空所有值
*/
@Deprecated
public void clearAttribute() {
dataMap.clear();
update();
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.has(key) </h1>
* 是否含有指定key
*
* @param key 是否含有指定值
* @return 是否含有
*/
@Deprecated
public boolean containsAttribute(String key) {
return dataMap.containsKey(key);
}
/**
* <h1> 此函数设计已过时,未来版本可能移除此类,请及时更换为: session.keys() </h1>
* 返回当前session会话所有key
*
* @return 所有值的key列表
*/
@Deprecated
public Set<String> attributeKeys() {
return dataMap.keySet();
}
}
@@ -11,12 +11,12 @@ import cn.dev33.satoken.SaManager;
public class SaSessionCustomUtil {
/**
* 添加上指定前缀,防止恶意伪造session
* 添加上指定前缀,防止恶意伪造Session
*/
public static String sessionKey = "custom";
/**
* 组织一下自定义Session的id
* 拼接Key: 自定义Session的Id
*
* @param sessionId 会话id
* @return sessionId
@@ -26,9 +26,9 @@ public class SaSessionCustomUtil {
}
/**
* 验证指定key的Session是否存在
* 指定key的Session是否存在
*
* @param sessionId session的id
* @param sessionId Session的id
* @return 是否存在
*/
public static boolean isExists(String sessionId) {
@@ -62,7 +62,7 @@ public class SaSessionCustomUtil {
}
/**
* 删除指定key的session
* 删除指定key的Session
*
* @param sessionId 指定key
*/
@@ -3,7 +3,7 @@ package cn.dev33.satoken.session;
import java.io.Serializable;
/**
* token签名 Model
* Token签名 Model
*
* 挂在到SaSession上的token签名
*
@@ -0,0 +1,71 @@
package cn.dev33.satoken.sso;
/**
* Sa-Token-SSO模块相关常量
* @author kong
*
*/
public class SaSsoConsts {
/**
* 所有API接口
* @author kong
*/
public static final class Api {
/** SSO-Server端:授权地址 */
public static String ssoAuth = "/sso/auth";
/** SSO-Server端:RestAPI 登录接口 */
public static String ssoDoLogin = "/sso/doLogin";
/** SSO-Server端:校验ticket 获取账号id */
public static String ssoCheckTicket = "/sso/checkTicket";
/** SSO-Server端 (and Client端):单点注销 */
public static String ssoLogout = "/sso/logout";
/** SSO-Client端:登录地址 */
public static String ssoLogin = "/sso/login";
/** SSO-Client端:单点注销的回调 */
public static String ssoLogoutCall = "/sso/logoutCall";
}
/**
* 所有参数名称
* @author kong
*/
public static final class ParamName {
/** redirect参数名称 */
public static String redirect = "redirect";
/** ticket参数名称 */
public static String ticket = "ticket";
/** back参数名称 */
public static String back = "back";
/** loginId参数名称 */
public static String loginId = "loginId";
/** secretkey参数名称 */
public static String secretkey = "secretkey";
/** Client端单点注销时-回调URL 参数名称 */
public static String ssoLogoutCall = "ssoLogoutCall";
}
/** Client端单点注销回调URL的Set集合,存储在Session中使用的key */
public static final String SLO_CALLBACK_SET_KEY = "SLO_CALLBACK_SET_KEY_";
/** 表示OK的返回结果 */
public static final String OK = "ok";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
}
@@ -0,0 +1,180 @@
package cn.dev33.satoken.sso;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.SaSsoConsts.Api;
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO 单点登录请求处理类封装
* @author kong
*
*/
public class SaSsoHandle {
/**
* 处理Server端请求
* @return 处理结果
*/
public static Object serverRequest() {
// 获取变量
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig sso = SaManager.getConfig().getSso();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// ---------- SSO-Server端:单点登录授权地址
if(req.isPath(Api.ssoAuth)) {
// ---------- 此处两种情况分开处理:
// 情况1:在SSO认证中心尚未登录,则先去登登录
if(stpLogic.isLogin() == false) {
return sso.notLoginView.get();
}
// 情况2:在SSO认证中心已经登录,开始构建授权重定向地址,下放ticket
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
return res.redirect(redirectUrl);
}
// ---------- SSO-Server端:RestAPI 登录接口
if(req.isPath(Api.ssoDoLogin)) {
return sso.doLoginHandle.apply(req.getParam("name"), req.getParam("pwd"));
}
// ---------- SSO-Server端:校验ticket 获取账号id
if(req.isPath(Api.ssoCheckTicket) && sso.isHttp) {
String ticket = req.getParam(ParamName.ticket);
String sloCallback = req.getParam(ParamName.ssoLogoutCall);
// 校验ticket,获取对应的账号id
Object loginId = SaSsoUtil.checkTicket(ticket);
// 注册此客户端的单点注销回调URL
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
// 返回给Client端
return loginId;
}
// ---------- SSO-Server端:单点注销
if(req.isPath(Api.ssoLogout) && sso.isSlo) {
String loginId = req.getParam(ParamName.loginId);
String secretkey = req.getParam(ParamName.secretkey);
// 遍历通知Client端注销会话
SaSsoUtil.singleLogout(secretkey, loginId, url -> sso.sendHttp.apply(url));
// 完成
return SaSsoConsts.OK;
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* 处理Client端请求
* @return 处理结果
*/
public static Object clientRequest() {
// 获取变量
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig sso = SaManager.getConfig().getSso();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// ---------- SSO-Client端:登录地址
if(req.isPath(Api.ssoLogin)) {
String back = req.getParam(ParamName.back, "/");
String ticket = req.getParam(ParamName.ticket);
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
if(stpLogic.isLogin()) {
return res.redirect(back);
}
/*
* 此时有两种情况:
* 情况1ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
*/
if(ticket == null) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket,获取账号id
Object loginId = null;
if(sso.isHttp) {
// 方式1:使用http请求校验ticket
String ssoLogoutCall = null;
if(sso.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = sso.sendHttp.apply(checkUrl);
loginId = (SaFoxUtil.isEmpty(body) ? null : body);
} else {
// 方式2:直连Redis校验ticket
loginId = SaSsoUtil.checkTicket(ticket);
}
// ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址
if(loginId != null ) {
stpLogic.login(loginId);
return res.redirect(back);
} else {
// 如果ticket无效:
return sso.ticketInvalidView.apply(ticket);
}
}
}
// ---------- SSO-Client端:单点注销 [模式二]
if(req.isPath(Api.ssoLogout) && sso.isSlo && sso.isHttp == false) {
stpLogic.logout();
if(req.getParam(ParamName.back) == null) {
return SaResult.ok("单点注销成功");
} else {
return res.redirect(req.getParam(ParamName.back, "/"));
}
}
// ---------- SSO-Client端:单点注销 [模式三]
if(req.isPath(Api.ssoLogout) && sso.isSlo && sso.isHttp) {
// 如果未登录,则无需注销
if(stpLogic.isLogin() == false) {
return SaResult.ok();
}
// 调用SSO-Server认证中心API
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
String body = String.valueOf(sso.sendHttp.apply(url));
if(SaSsoConsts.OK.equals(body)) {
if(req.getParam(ParamName.back) == null) {
return SaResult.ok("单点注销成功");
} else {
return res.redirect(req.getParam(ParamName.back, "/"));
}
}
return SaResult.error("单点注销失败");
}
// ---------- SSO-Client端:单点注销的回调 [模式三]
if(req.isPath(Api.ssoLogoutCall) && sso.isSlo && sso.isHttp) {
String loginId = req.getParam(ParamName.loginId);
String secretkey = req.getParam(ParamName.secretkey);
SaSsoUtil.checkSecretkey(secretkey);
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
return SaSsoConsts.OK;
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
}
@@ -0,0 +1,331 @@
package cn.dev33.satoken.sso;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Sa-Token-SSO 单点登录模块
* @author kong
*
*/
public class SaSsoTemplate {
public StpLogic stpLogic;
public SaSsoTemplate(StpLogic stpLogic) {
this.stpLogic = stpLogic;
}
/**
* 创建一个 Ticket码
* @param loginId 账号id
* @return 票据
*/
public String createTicket(Object loginId) {
// 随机一个ticket
String ticket = randomTicket(loginId);
// 保存入库
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingKeyTicketToId(ticket), String.valueOf(loginId), ticketTimeout);
SaManager.getSaTokenDao().set(splicingKeyIdToTicket(loginId), String.valueOf(ticket), ticketTimeout);
// 返回
return ticket;
}
/**
* 删除一个 Ticket码
* @param ticket Ticket码
*/
public void deleteTicket(String ticket) {
Object loginId = getLoginId(ticket);
if(loginId != null) {
SaManager.getSaTokenDao().delete(splicingKeyTicketToId(ticket));
SaManager.getSaTokenDao().delete(splicingKeyIdToTicket(loginId));
}
}
/**
* 构建URLServer端向Client下放ticke的地址
* @param loginId 账号id
* @param redirect Client端提供的重定向地址
* @return see note
*/
public String buildRedirectUrl(Object loginId, String redirect) {
// 校验重定向地址
checkRedirectUrl(redirect);
// 删掉旧ticket
String oldTicket = SaManager.getSaTokenDao().get(splicingKeyIdToTicket(loginId));
if(oldTicket != null) {
deleteTicket(oldTicket);
}
// 获取新ticket
String ticket = createTicket(loginId);
// 构建 授权重定向地址
redirect = encodeBackParam(redirect);
String redirectUrl = SaFoxUtil.joinParam(redirect, ParamName.ticket, ticket);
return redirectUrl;
}
/**
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
* @param ticket Ticket码
* @return 账号id
*/
public Object getLoginId(String ticket) {
if(SaFoxUtil.isEmpty(ticket)) {
return null;
}
return SaManager.getSaTokenDao().get(splicingKeyTicketToId(ticket));
}
/**
* 根据 Ticket码 获取账号id,并转换为指定类型
* @param <T> 要转换的类型
* @param ticket Ticket码
* @param cs 要转换的类型
* @return 账号id
*/
public <T> T getLoginId(String ticket, Class<T> cs) {
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
}
/**
* 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除
* @param ticket Ticket码
* @return 账号id
*/
public Object checkTicket(String ticket) {
Object loginId = getLoginId(ticket);
if(loginId != null) {
deleteTicket(ticket);
}
return loginId;
}
/**
* 校验重定向url合法性
* @param url 下放ticket的url地址
*/
public void checkRedirectUrl(String url) {
// 1、是否是一个有效的url
if(SaFoxUtil.isUrl(url) == false) {
throw new SaTokenException("无效回调地址:" + url);
}
// 2、截取掉?后面的部分
int qIndex = url.indexOf("?");
if(qIndex != -1) {
url = url.substring(0, qIndex);
}
// 3、是否在[允许地址列表]之中
String authUrl = SaManager.getConfig().getSso().getAllowUrl().replaceAll(" ", "");
List<String> authUrlList = Arrays.asList(authUrl.split(","));
if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) {
throw new SaTokenException("非法回调地址:" + url);
}
// 验证通过
return;
}
/**
* 构建URLServer端 单点登录地址
* @param clientLoginUrl Client端登录地址
* @param back 回调路径
* @return [SSO-Server端-认证地址 ]
*/
public String buildServerAuthUrl(String clientLoginUrl, String back) {
// 服务端认证地址
String serverUrl = SaManager.getConfig().getSso().getAuthUrl();
// 对back地址编码
back = (back == null ? "" : back);
back = SaFoxUtil.encodeUrl(back);
// 拼接最终地址,格式示例:serverAuthUrl = http://xxx.com?redirectUrl=xxx.com?back=xxx.com
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, ParamName.back, back);
String serverAuthUrl = SaFoxUtil.joinParam(serverUrl, ParamName.redirect, clientLoginUrl);
// 返回
return serverAuthUrl;
}
/**
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
* @param url url
* @return 编码过后的url
*/
public String encodeBackParam(String url) {
// 获取back参数所在位置
int index = url.indexOf("?" + ParamName.back + "=");
if(index == -1) {
index = url.indexOf("&" + ParamName.back + "=");
if(index == -1) {
return url;
}
}
// 开始编码
int length = ParamName.back.length() + 2;
String back = url.substring(index + length);
back = SaFoxUtil.encodeUrl(back);
// 放回url中
url = url.substring(0, index + length) + back;
return url;
}
/**
* 随机一个 Ticket码
* @param loginId 账号id
* @return 票据
*/
public String randomTicket(Object loginId) {
return SaFoxUtil.getRandomString(64);
}
// ------------------- SSO 模式三 -------------------
/**
* 校验secretkey秘钥是否有效
* @param secretkey 秘钥
*/
public void checkSecretkey(String secretkey) {
if(secretkey == null || secretkey.isEmpty() || secretkey.equals(SaManager.getConfig().getSso().getSecretkey()) == false) {
throw new SaTokenException("无效秘钥:" + secretkey);
}
}
/**
* 构建URL:校验ticket的URL
* @param ticket ticket码
* @param ssoLogoutCallUrl 单点注销时的回调URL
* @return 构建完毕的URL
*/
public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
String url = SaManager.getConfig().getSso().getCheckTicketUrl();
// 拼接ticket参数
url = SaFoxUtil.joinParam(url, ParamName.ticket, ticket);
// 拼接单点注销时的回调URL
if(ssoLogoutCallUrl != null) {
url = SaFoxUtil.joinParam(url, ParamName.ssoLogoutCall, ssoLogoutCallUrl);
}
// 返回
return url;
}
/**
* 为指定账号id注册单点注销回调URL
* @param loginId 账号id
* @param sloCallbackUrl 单点注销时的回调URL
*/
public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
if(loginId == null || sloCallbackUrl == null || sloCallbackUrl.isEmpty()) {
return;
}
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet<String>());
urlSet.add(sloCallbackUrl);
stpLogic.getSessionByLoginId(loginId).set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
}
/**
* 循环调用Client端单点注销回调
* @param loginId 账号id
* @param fun 调用方法
*/
public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
String secretkey = SaManager.getConfig().getSso().getSecretkey();
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY,
() -> new HashSet<String>());
for (String url : urlSet) {
// 拼接:login参数、秘钥参数
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
// 调用
fun.run(url);
}
}
/**
* 构建URL:单点注销URL
* @param loginId 要注销的账号id
* @return 单点注销URL
*/
public String buildSloUrl(Object loginId) {
SaSsoConfig ssoConfig = SaManager.getConfig().getSso();
String url = ssoConfig.getSloUrl();
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
url = SaFoxUtil.joinParam(url, ParamName.secretkey, ssoConfig.getSecretkey());
return url;
}
/**
* 指定账号单点注销
* @param secretkey 校验秘钥
* @param loginId 指定账号
* @param fun 调用方法
*/
public void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) {
// step.1 校验秘钥
checkSecretkey(secretkey);
// step.2 遍历通知Client端注销会话
forEachSloUrl(loginId, fun);
// step.3 Server端注销
// StpUtil.logoutByLoginId(loginId);
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
}
// ------------------- 返回相应key -------------------
/**
* 拼接keyTicket 查 账号Id
* @param ticket ticket值
* @return key
*/
public String splicingKeyTicketToId(String ticket) {
return SaManager.getConfig().getTokenName() + ":ticket:" + ticket;
}
/**
* 拼接key:账号Id 反查 Ticket
* @param id 账号id
* @return key
*/
public String splicingKeyIdToTicket(Object id) {
return SaManager.getConfig().getTokenName() + ":id-ticket:" + id;
}
@FunctionalInterface
static interface CallSloUrlFunction{
/**
* 调用function
* @param url 注销回调URL
*/
public void run(String url);
}
}
@@ -0,0 +1,150 @@
package cn.dev33.satoken.sso;
import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token-SSO 单点登录工具类
* @author kong
*
*/
public class SaSsoUtil {
/**
* 底层 SaSsoTemplate 对象
*/
public static SaSsoTemplate saSsoTemplate = new SaSsoTemplate(StpUtil.stpLogic);
/**
* 创建一个 Ticket票据
* @param loginId 账号id
* @return 票据
*/
public static String createTicket(Object loginId) {
return saSsoTemplate.createTicket(loginId);
}
/**
* 删除一个 Ticket码
* @param ticket Ticket码
*/
public static void deleteTicket(String ticket) {
saSsoTemplate.deleteTicket(ticket);
}
/**
* 构建URLServer端向Client下放ticke的地址
* @param loginId 账号id
* @param redirect Client端提供的重定向地址
* @return see note
*/
public static String buildRedirectUrl(Object loginId, String redirect) {
return saSsoTemplate.buildRedirectUrl(loginId, redirect);
}
/**
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
* @param ticket Ticket码
* @return 账号id
*/
public static Object getLoginId(String ticket) {
return saSsoTemplate.getLoginId(ticket);
}
/**
* 根据 Ticket码 获取账号id,并转换为指定类型
* @param <T> 要转换的类型
* @param ticket Ticket码
* @param cs 要转换的类型
* @return 账号id
*/
public static <T> T getLoginId(String ticket, Class<T> cs) {
return saSsoTemplate.getLoginId(ticket, cs);
}
/**
* 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除
* @param ticket Ticket码
* @return 账号id
*/
public static Object checkTicket(String ticket) {
return saSsoTemplate.checkTicket(ticket);
}
/**
* 校验重定向url合法性
* @param url 下放ticket的url地址
*/
public static void checkAuthUrl(String url) {
saSsoTemplate.checkRedirectUrl(url);
}
/**
* 构建URLServer端 单点登录地址
* @param clientLoginUrl Client端登录地址
* @param back 回调路径
* @return [SSO-Server端-认证地址 ]
*/
public static String buildServerAuthUrl(String clientLoginUrl, String back) {
return saSsoTemplate.buildServerAuthUrl(clientLoginUrl, back);
}
// ------------------- SSO 模式三 -------------------
/**
* 校验secretkey秘钥是否有效
* @param secretkey 秘钥
*/
public static void checkSecretkey(String secretkey) {
saSsoTemplate.checkSecretkey(secretkey);
}
/**
* 构建URL:校验ticket的URL
* @param ticket ticket码
* @param ssoLogoutCallUrl 单点注销时的回调URL
* @return 构建完毕的URL
*/
public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
return saSsoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
}
/**
* 为指定账号id注册单点注销回调URL
* @param loginId 账号id
* @param sloCallbackUrl 单点注销时的回调URL
*/
public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl);
}
/**
* 循环调用Client端单点注销回调
* @param loginId 账号id
* @param fun 调用方法
*/
public static void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
saSsoTemplate.forEachSloUrl(loginId, fun);
}
/**
* 构建URL:单点注销URL
* @param loginId 要注销的账号id
* @return 单点注销URL
*/
public static String buildSloUrl(Object loginId) {
return saSsoTemplate.buildSloUrl(loginId);
}
/**
* 指定账号单点注销
* @param secretkey 校验秘钥
* @param loginId 指定账号
* @param fun 调用方法
*/
public static void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) {
saSsoTemplate.singleLogout(secretkey, loginId, fun);
}
}
@@ -30,14 +30,14 @@ public class SaLoginModel {
/**
* @return device
* @return 参考 {@link #device}
*/
public String getDevice() {
return device;
}
/**
* @param device 要设置的 device
* @param device 参考 {@link #device}
* @return 对象自身
*/
public SaLoginModel setDevice(String device) {
@@ -46,14 +46,14 @@ public class SaLoginModel {
}
/**
* @return isLastingCookie
* @return 参考 {@link #isLastingCookie}
*/
public Boolean getIsLastingCookie() {
return isLastingCookie;
}
/**
* @param isLastingCookie 要设置的 isLastingCookie
* @param isLastingCookie 参考 {@link #isLastingCookie}
* @return 对象自身
*/
public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
@@ -62,14 +62,14 @@ public class SaLoginModel {
}
/**
* @return timeout
* @return 参考 {@link #timeout}
*/
public Long getTimeout() {
return timeout;
}
/**
* @param timeout 要设置的 timeout
* @param timeout 参考 {@link #timeout}
* @return 对象自身
*/
public SaLoginModel setTimeout(long timeout) {
@@ -79,7 +79,7 @@ public class SaLoginModel {
/**
* @return cookie时长
* @return Cookie时长
*/
public int getCookieTimeout() {
if(isLastingCookie == false) {
@@ -91,7 +91,6 @@ public class SaLoginModel {
return (int)(long)timeout;
}
/**
* 构建对象,初始化默认值
* @return 对象自身
@@ -118,7 +117,6 @@ public class SaLoginModel {
return this;
}
/**
* 静态方法获取一个 SaLoginModel 对象
* @return SaLoginModel 对象
@@ -127,7 +125,6 @@ public class SaLoginModel {
return new SaLoginModel();
}
/**
* toString
*/
@@ -136,7 +133,4 @@ public class SaLoginModel {
return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]";
}
}
@@ -1,7 +1,7 @@
package cn.dev33.satoken.stp;
/**
* token信息Model: 用来描述一个token的常用参数
* Token信息Model: 用来描述一个Token的常用参数
*
* @author kong
*
@@ -38,6 +38,11 @@ public class SaTokenInfo {
/** 登录设备标识 */
public String loginDevice;
/** 自定义数据 */
public String tag;
/**
* @return token名称
*/
@@ -178,6 +183,20 @@ public class SaTokenInfo {
this.loginDevice = loginDevice;
}
/**
* @return 自定义数据
*/
public String getTag() {
return tag;
}
/**
* @param tag 自定义数据
*/
public void setTag(String tag) {
this.tag = tag;
}
/**
* toString
*/
@@ -186,7 +205,10 @@ public class SaTokenInfo {
return "SaTokenInfo [tokenName=" + tokenName + ", tokenValue=" + tokenValue + ", isLogin=" + isLogin
+ ", loginId=" + loginId + ", loginType=" + loginType + ", tokenTimeout=" + tokenTimeout
+ ", sessionTimeout=" + sessionTimeout + ", tokenSessionTimeout=" + tokenSessionTimeout
+ ", tokenActivityTimeout=" + tokenActivityTimeout + ", loginDevice=" + loginDevice + "]";
+ ", tokenActivityTimeout=" + tokenActivityTimeout + ", loginDevice=" + loginDevice + ", tag=" + tag
+ "]";
}
}
@@ -10,7 +10,7 @@ import java.util.List;
public interface StpInterface {
/**
* 返回指定 LoginId 所拥有的权限码集合
* 返回指定账号id所拥有的权限码集合
*
* @param loginId 账号id
* @param loginType 账号类型
@@ -19,7 +19,7 @@ public interface StpInterface {
public List<String> getPermissionList(Object loginId, String loginType);
/**
* 返回指定loginId所拥有的角色标识集合
* 返回指定账号id所拥有的角色标识集合
*
* @param loginId 账号id
* @param loginType 账号类型
@@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* 对StpInterface接口默认的实现类
* 对 {@link StpInterface} 接口默认的实现类
* <p>
* 如果开发者没有实现StpInterface接口,则使用此默认实现
*
@@ -9,6 +9,7 @@ import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
@@ -20,6 +21,7 @@ import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.NotSafeException;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
@@ -66,7 +68,7 @@ public class StpLogic {
}
// =================== 获取token 相关 ===================
// ------------------- 获取token 相关 -------------------
/**
* 返回token名称
@@ -77,7 +79,7 @@ public class StpLogic {
}
/**
* 创建一个tokenValue
* 创建一个TokenValue
* @param loginId loginId
* @return 生成的tokenValue
*/
@@ -86,14 +88,16 @@ public class StpLogic {
}
/**
* 在当前会话写入当前tokenValue
* 在当前会话写入当前TokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间(秒)
*/
public void setTokenValue(String tokenValue, int cookieTimeout){
SaTokenConfig config = getConfig();
// 将token保存到[存储器]里
SaStorage storage = SaHolder.getStorage();
// 判断是否配置了token前缀
String tokenPrefix = config.getTokenPrefix();
if(SaFoxUtil.isEmpty(tokenPrefix)) {
@@ -111,7 +115,7 @@ public class StpLogic {
}
/**
* 获取当前tokenValue
* 获取当前TokenValue
* @return 当前tokenValue
*/
public String getTokenValue(){
@@ -128,7 +132,7 @@ public class StpLogic {
}
// 2. 尝试从请求体里面读取
if(tokenValue == null && config.getIsReadBody()){
tokenValue = request.getParameter(keyTokenName);
tokenValue = request.getParam(keyTokenName);
}
// 3. 尝试从header里读取
if(tokenValue == null && config.getIsReadHead()){
@@ -155,7 +159,7 @@ public class StpLogic {
}
/**
* 获取当前会话的token信息
* 获取当前会话的Token信息
* @return token信息
*/
public SaTokenInfo getTokenInfo() {
@@ -174,7 +178,7 @@ public class StpLogic {
}
// =================== 登录相关操作 ===================
// ------------------- 登录相关操作 -------------------
// 登录与注销
@@ -254,13 +258,10 @@ public class StpLogic {
tokenValue = createTokenValue(id);
}
// ------ 3. 获取[User-Session] (如果还没有创建session, 则新建, 如果已经创建,则续期)
SaSession session = getSessionByLoginId(id, false);
if(session == null) {
session = getSessionByLoginId(id);
} else {
session.updateMinTimeout(loginModel.getTimeout());
}
// ------ 3. 获取[User-Session], 续期
SaSession session = getSessionByLoginId(id, true);
session.updateMinTimeout(loginModel.getTimeout());
// 在session上记录token签名
session.addTokenSign(new TokenSign(tokenValue, loginModel.getDevice()));
@@ -279,7 +280,7 @@ public class StpLogic {
}
/**
* 当前会话注销登录
* 会话注销
*/
public void logout() {
// 如果连token都没有,那么无需执行任何操作
@@ -295,7 +296,7 @@ public class StpLogic {
}
/**
* 指定token的会话注销登录
* 会话注销,根据指定Token
* @param tokenValue 指定token
*/
public void logoutByTokenValue(String tokenValue) {
@@ -327,7 +328,7 @@ public class StpLogic {
}
/**
* 指定账号id的会话注销登录(踢人下线)
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
@@ -336,8 +337,8 @@ public class StpLogic {
}
/**
* 指定账号id指定设备的会话注销登录(踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
@@ -368,54 +369,10 @@ public class StpLogic {
session.logoutByTokenSignCountToZero();
}
/**
* 封禁指定账号
* <p> 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public void disable(Object loginId, long disableTime) {
// 标注为已被封禁
SaManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
// $$ 通知监听器
SaManager.getSaTokenListener().doDisable(loginType, loginId, disableTime);
}
/**
* 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
* @param loginId 账号id
* @return see note
*/
public boolean isDisable(Object loginId) {
return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
}
/**
* 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
* @param loginId 账号id
* @return see note
*/
public long getDisableTime(Object loginId) {
return SaManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public void untieDisable(Object loginId) {
SaManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
// $$ 通知监听器
SaManager.getSaTokenListener().doUntieDisable(loginType, loginId);
}
// 查询相关
/**
* 获取当前会话是否已经登录
* 当前会话是否已经登录
* @return 是否已登录
*/
public boolean isLogin() {
@@ -472,7 +429,7 @@ public class StpLogic {
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* 获取当前会话账号id, 如果未登录,则返回默认值
* @param <T> 返回类型
* @param defaultValue 默认值
* @return 登录id
@@ -498,7 +455,7 @@ public class StpLogic {
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* 获取当前会话账号id, 如果未登录,则返回null
* @return 账号id
*/
public Object getLoginIdDefaultNull() {
@@ -525,7 +482,7 @@ public class StpLogic {
}
/**
* 获取当前会话登录id, 并转换为String
* 获取当前会话账号id, 并转换为String类型
* @return 账号id
*/
public String getLoginIdAsString() {
@@ -533,7 +490,7 @@ public class StpLogic {
}
/**
* 获取当前会话登录id, 并转换为int
* 获取当前会话账号id, 并转换为int类型
* @return 账号id
*/
public int getLoginIdAsInt() {
@@ -541,7 +498,7 @@ public class StpLogic {
}
/**
* 获取当前会话登录id, 并转换为long
* 获取当前会话账号id, 并转换为long类型
* @return 账号id
*/
public long getLoginIdAsLong() {
@@ -549,9 +506,9 @@ public class StpLogic {
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* 获取指定Token对应的账号id,如果未登录,则返回 null
* @param tokenValue token
* @return 登录id
* @return 账号id
*/
public Object getLoginIdByToken(String tokenValue) {
if(tokenValue == null) {
@@ -561,22 +518,22 @@ public class StpLogic {
}
/**
* 获取指定token对应的登录id (不做任何特殊处理)
* 获取指定Token对应的账号id (不做任何特殊处理)
* @param tokenValue token值
* @return loginId
* @return 账号id
*/
public String getLoginIdNotHandle(String tokenValue) {
return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
}
// =================== session相关 ===================
// ------------------- Session相关 -------------------
/**
* 获取指定key的session, 如果session尚未创建,isCreate=是否新建并返回
* @param sessionId sessionId
* 获取指定key的Session, 如果Session尚未创建,isCreate=是否新建并返回
* @param sessionId SessionId
* @param isCreate 是否新建
* @return session对象
* @return Session对象
*/
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
@@ -588,76 +545,76 @@ public class StpLogic {
}
/**
* 获取指定key的session, 如果session尚未创建,则返回null
* @param sessionId sessionId
* @return session对象
* 获取指定key的Session, 如果Session尚未创建,则返回null
* @param sessionId SessionId
* @return Session对象
*/
public SaSession getSessionBySessionId(String sessionId) {
return getSessionBySessionId(sessionId, false);
}
/**
* 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
* @return Session对象
*/
public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return getSessionBySessionId(splicingKeySession(loginId), isCreate);
}
/**
* 获取指定loginId的session,如果session尚未创建,则新建并返回
* 获取指定账号id的Session,如果Session尚未创建,则新建并返回
* @param loginId 账号id
* @return session会话
* @return Session对象
*/
public SaSession getSessionByLoginId(Object loginId) {
return getSessionByLoginId(loginId, true);
}
/**
* 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回
* 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回
* @param isCreate 是否新建
* @return 当前会话的session
* @return Session对象
*/
public SaSession getSession(boolean isCreate) {
return getSessionByLoginId(getLoginId(), isCreate);
}
/**
* 获取当前会话的session,如果session尚未创建,则新建并返回
* @return 当前会话的session
* 获取当前会话的Session,如果Session尚未创建,则新建并返回
* @return Session对象
*/
public SaSession getSession() {
return getSession(true);
}
// =================== token专属session ===================
// ------------------- token专属session -------------------
/**
* 获取指定token的专属session,如果session尚未创建,isCreate代表是否新建并返回
* 获取指定Token-Session,如果Session尚未创建,isCreate代表是否新建并返回
* @param tokenValue token值
* @param isCreate 是否新建
* @return session会话
* @return session对象
*/
public SaSession getTokenSessionByToken(String tokenValue, boolean isCreate) {
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate);
}
/**
* 获取指定token的专属session,如果session尚未创建,则新建并返回
* @param tokenValue token值
* @return session会话
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
* @param tokenValue Token值
* @return Session对象
*/
public SaSession getTokenSessionByToken(String tokenValue) {
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), true);
}
/**
* 获取当前token的专属-session,如果session尚未创建,isCreate代表是否新建并返回
* 获取当前Token-Session,如果Session尚未创建,isCreate代表是否新建并返回
* @param isCreate 是否新建
* @return session会话
* @return Session对象
*/
public SaSession getTokenSession(boolean isCreate) {
// 如果配置了需要校验登录状态,则验证一下
@@ -681,15 +638,15 @@ public class StpLogic {
}
/**
* 获取当前token的专属-session,如果session尚未创建,则新建并返回
* @return session会话
* 获取当前Token-Session,如果Session尚未创建,则新建并返回
* @return Session对象
*/
public SaSession getTokenSession() {
return getTokenSession(true);
}
// =================== [临时过期] 验证相关 ===================
// ------------------- [临时过期] 验证相关 -------------------
/**
* 写入指定token的 [最后操作时间] 为当前时间戳
@@ -779,7 +736,7 @@ public class StpLogic {
}
// =================== 过期时间相关 ===================
// ------------------- 过期时间相关 -------------------
/**
* 获取当前登录者的token剩余有效时间 (单位: 秒)
@@ -874,7 +831,7 @@ public class StpLogic {
}
// =================== 角色验证操作 ===================
// ------------------- 角色验证操作 -------------------
/**
* 指定账号id是否含有角色标识, 返回true或false
@@ -940,7 +897,7 @@ public class StpLogic {
}
// =================== 权限验证操作 ===================
// ------------------- 权限验证操作 -------------------
/**
* 指定账号id是否含有指定权限, 返回true或false
@@ -1006,10 +963,10 @@ public class StpLogic {
}
// =================== id 反查token 相关操作 ===================
// ------------------- id 反查token 相关操作 -------------------
/**
* 获取指定loginId的tokenValue
* 获取指定账号id的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
@@ -1020,7 +977,7 @@ public class StpLogic {
}
/**
* 获取指定loginId指定设备端的tokenValue
* 获取指定账号id指定设备端的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
@@ -1033,7 +990,7 @@ public class StpLogic {
}
/**
* 获取指定loginId的tokenValue集合
* 获取指定账号id的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
@@ -1042,7 +999,7 @@ public class StpLogic {
}
/**
* 获取指定loginId指定设备端的tokenValue 集合
* 获取指定账号id指定设备端的tokenValue 集合
* @param loginId 账号id
* @param device 设备标识
* @return 此loginId的所有相关token
@@ -1065,7 +1022,7 @@ public class StpLogic {
}
/**
* 返回当前token的登录设备
* 返回当前会话的登录设备
* @return 当前令牌的登录设备
*/
public String getLoginDevice() {
@@ -1094,10 +1051,10 @@ public class StpLogic {
}
// =================== 会话管理 ===================
// ------------------- 会话管理 -------------------
/**
* 根据条件查询token
* 根据条件查询Token
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
@@ -1119,7 +1076,7 @@ public class StpLogic {
}
/**
* 根据条件查询token专属Session的Id
* 根据条件查询Token专属Session的Id
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
@@ -1130,90 +1087,7 @@ public class StpLogic {
}
// =================== 返回相应key ===================
/**
* 获取key:客户端 tokenName
* @return key
*/
public String splicingKeyTokenName() {
return getConfig().getTokenName();
}
/**
* 获取key tokenValue 持久化 token-id
* @param tokenValue token值
* @return key
*/
public String splicingKeyTokenValue(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":token:" + tokenValue;
}
/**
* 获取key session 持久化
* @param loginId 账号id
* @return key
*/
public String splicingKeySession(Object loginId) {
return getConfig().getTokenName() + ":" + loginType + ":session:" + loginId;
}
/**
* 获取key tokenValue的专属session
* @param tokenValue token值
* @return key
*/
public String splicingKeyTokenSession(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":token-session:" + tokenValue;
}
/**
* 获取key: 指定token的最后操作时间 持久化
* @param tokenValue token值
* @return key
*/
public String splicingKeyLastActivityTime(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":last-activity:" + tokenValue;
}
/**
* 在进行身份切换时,使用的存储key
* @return key
*/
public String splicingKeySwitch() {
return SaTokenConsts.SWITCH_TO_SAVE_KEY + loginType;
}
/**
* 如果token为本次请求新创建的,则以此字符串为key存储在当前request中
* @return key
*/
public String splicingKeyJustCreatedSave() {
return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginType;
}
/**
* 拼接key 账号封禁
* @param loginId 账号id
* @return key
*/
public String splicingKeyDisable(Object loginId) {
return getConfig().getTokenName() + ":" + loginType + ":disable:" + loginId;
}
// =================== Bean对象代理 ===================
/**
* 返回配置对象
* @return 配置对象
*/
public SaTokenConfig getConfig() {
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
return SaManager.getConfig();
}
// =================== 其它方法 ===================
// ------------------- 其它方法 -------------------
/**
* 根据注解(@SaCheckLogin)鉴权
@@ -1249,11 +1123,65 @@ public class StpLogic {
}
}
/**
* 根据注解(@SaCheckSafe)鉴权
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckSafe at) {
this.checkSafe();
}
// =================== 身份切换 ===================
// ------------------- 账号封禁 -------------------
/**
* 临时切换身份为指定loginId
* 封禁指定账号
* <p> 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public void disable(Object loginId, long disableTime) {
// 标注为已被封禁
SaManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
// $$ 通知监听器
SaManager.getSaTokenListener().doDisable(loginType, loginId, disableTime);
}
/**
* 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
* @param loginId 账号id
* @return see note
*/
public boolean isDisable(Object loginId) {
return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
}
/**
* 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
* @param loginId 账号id
* @return see note
*/
public long getDisableTime(Object loginId) {
return SaManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public void untieDisable(Object loginId) {
SaManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
// $$ 通知监听器
SaManager.getSaTokenListener().doUntieDisable(loginType, loginId);
}
// ------------------- 身份切换 -------------------
/**
* 临时切换身份为指定账号id
* @param loginId 指定loginId
*/
public void switchTo(Object loginId) {
@@ -1284,8 +1212,8 @@ public class StpLogic {
}
/**
* 在一个代码段里方法内,临时切换身份为指定loginId
* @param loginId 指定loginId
* 在一个代码段里方法内,临时切换身份为指定账号id
* @param loginId 指定账号id
* @param function 要执行的方法
*/
public void switchTo(Object loginId, SaFunction function) {
@@ -1298,6 +1226,142 @@ public class StpLogic {
endSwitch();
}
}
// ------------------- 二级认证 -------------------
/**
* 在当前会话 开启二级认证
* @param safeTime 维持时间 (单位: 秒)
*/
public void openSafe(long safeTime) {
long eff = System.currentTimeMillis() + safeTime * 1000;
getTokenSession().set(SaTokenConsts.SAFE_AUTH_SAVE_KEY, eff);
}
/**
* 当前会话 是否处于二级认证时间内
* @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
*/
public boolean isSafe() {
long eff = getTokenSession().get(SaTokenConsts.SAFE_AUTH_SAVE_KEY, 0L);
if(eff == 0 || eff < System.currentTimeMillis()) {
return false;
}
return true;
}
/**
* 检查当前会话是否已通过二级认证,如未通过则抛出异常
*/
public void checkSafe() {
if (isSafe() == false) {
throw new NotSafeException();
}
}
/**
* 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
* @return 剩余有效时间
*/
public long getSafeTime() {
long eff = getTokenSession().get(SaTokenConsts.SAFE_AUTH_SAVE_KEY, 0L);
if(eff == 0 || eff < System.currentTimeMillis()) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
return (eff - System.currentTimeMillis()) / 1000;
}
/**
* 在当前会话 结束二级认证
*/
public void closeSafe() {
getTokenSession().delete(SaTokenConsts.SAFE_AUTH_SAVE_KEY);
}
// ------------------- 返回相应key -------------------
/**
* 拼接key:客户端 tokenName
* @return key
*/
public String splicingKeyTokenName() {
return getConfig().getTokenName();
}
/**
* 拼接key tokenValue 持久化 token-id
* @param tokenValue token值
* @return key
*/
public String splicingKeyTokenValue(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":token:" + tokenValue;
}
/**
* 拼接key Session 持久化
* @param loginId 账号id
* @return key
*/
public String splicingKeySession(Object loginId) {
return getConfig().getTokenName() + ":" + loginType + ":session:" + loginId;
}
/**
* 拼接key tokenValue的专属session
* @param tokenValue token值
* @return key
*/
public String splicingKeyTokenSession(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":token-session:" + tokenValue;
}
/**
* 拼接key: 指定token的最后操作时间 持久化
* @param tokenValue token值
* @return key
*/
public String splicingKeyLastActivityTime(String tokenValue) {
return getConfig().getTokenName() + ":" + loginType + ":last-activity:" + tokenValue;
}
/**
* 在进行身份切换时,使用的存储key
* @return key
*/
public String splicingKeySwitch() {
return SaTokenConsts.SWITCH_TO_SAVE_KEY + loginType;
}
/**
* 如果token为本次请求新创建的,则以此字符串为key存储在当前request中
* @return key
*/
public String splicingKeyJustCreatedSave() {
return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginType;
}
/**
* 拼接key 账号封禁
* @param loginId 账号id
* @return key
*/
public String splicingKeyDisable(Object loginId) {
return getConfig().getTokenName() + ":" + loginType + ":disable:" + loginId;
}
// ------------------- Bean对象代理 -------------------
/**
* 返回配置对象
* @return 配置对象
*/
public SaTokenConfig getConfig() {
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
return SaManager.getConfig();
}
}
@@ -41,7 +41,7 @@ public class StpUtil {
}
/**
* 在当前会话写入当前tokenValue
* 在当前会话写入当前TokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间(秒)
*/
@@ -50,7 +50,7 @@ public class StpUtil {
}
/**
* 获取当前tokenValue
* 获取当前TokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
@@ -58,7 +58,7 @@ public class StpUtil {
}
/**
* 获取当前会话的token信息
* 获取当前会话的Token信息
* @return token信息
*/
public static SaTokenInfo getTokenInfo() {
@@ -104,14 +104,14 @@ public class StpUtil {
}
/**
* 当前会话注销登录
* 会话注销
*/
public static void logout() {
stpLogic.logout();
}
/**
* 指定token的会话注销登录
* 会话注销,根据指定Token
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
@@ -119,8 +119,8 @@ public class StpUtil {
}
/**
* 指定账号id的会话注销登录(踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
@@ -128,7 +128,7 @@ public class StpUtil {
}
/**
* 指定账号id指定设备的会话注销登录(踢人下线)
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
* @param device 设备标识
@@ -137,53 +137,17 @@ public class StpUtil {
stpLogic.logoutByLoginId(loginId, device);
}
/**
* 封禁指定账号
* <p> 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disable(Object loginId, long disableTime) {
stpLogic.disable(loginId, disableTime);
}
/**
* 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
* @param loginId 账号id
* @return see note
*/
public static boolean isDisable(Object loginId) {
return stpLogic.isDisable(loginId);
}
/**
* 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
* @param loginId 账号id
* @return see note
*/
public static long getDisableTime(Object loginId) {
return stpLogic.getDisableTime(loginId);
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public static void untieDisable(Object loginId) {
stpLogic.untieDisable(loginId);
}
// 查询相关
/**
* 获取当前会话是否已经登录
/**
* 当前会话是否已经登录
* @return 是否已登录
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public static void checkLogin() {
@@ -199,7 +163,7 @@ public class StpUtil {
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* 获取当前会话账号id, 如果未登录,则返回默认值
* @param <T> 返回类型
* @param defaultValue 默认值
* @return 登录id
@@ -209,7 +173,7 @@ public class StpUtil {
}
/**
* 获取当前会话登录id, 如果未登录,则返回null
* 获取当前会话账号id, 如果未登录,则返回null
* @return 账号id
*/
public static Object getLoginIdDefaultNull() {
@@ -217,7 +181,7 @@ public class StpUtil {
}
/**
* 获取当前会话登录id, 并转换为String
* 获取当前会话账号id, 并转换为String类型
* @return 账号id
*/
public static String getLoginIdAsString() {
@@ -225,7 +189,7 @@ public class StpUtil {
}
/**
* 获取当前会话登录id, 并转换为int
* 获取当前会话账号id, 并转换为int类型
* @return 账号id
*/
public static int getLoginIdAsInt() {
@@ -233,17 +197,17 @@ public class StpUtil {
}
/**
* 获取当前会话登录id, 并转换为long
* 获取当前会话账号id, 并转换为long类型
* @return 账号id
*/
public static long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
/**
* 获取指定Token对应的账号id,如果未登录,则返回 null
* @param tokenValue token
* @return 登录id
* @return 账号id
*/
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
@@ -252,46 +216,46 @@ public class StpUtil {
// =================== session相关 ===================
/**
* 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回
/**
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
* @return Session对象
*/
public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定key的session, 如果session尚未创建,则返回null
* @param sessionId sessionId
* @return session对象
* 获取指定key的Session, 如果Session尚未创建,则返回null
* @param sessionId SessionId
* @return Session对象
*/
public static SaSession getSessionBySessionId(String sessionId) {
return stpLogic.getSessionBySessionId(sessionId);
}
/**
* 获取指定loginId的session,如果session尚未创建,则新建并返回
* 获取指定账号id的Session,如果Session尚未创建,则新建并返回
* @param loginId 账号id
* @return session会话
* @return Session对象
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
}
/**
* 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回
* 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回
* @param isCreate 是否新建
* @return 当前会话的session
* @return Session对象
*/
public static SaSession getSession(boolean isCreate) {
return stpLogic.getSession(isCreate);
}
/**
* 获取当前会话的session,如果session尚未创建,则新建并返回
* @return 当前会话的session
* 获取当前会话的Session,如果Session尚未创建,则新建并返回
* @return Session对象
*/
public static SaSession getSession() {
return stpLogic.getSession();
@@ -301,17 +265,17 @@ public class StpUtil {
// =================== token专属session ===================
/**
* 获取指定token的专属session,如果session尚未创建,则新建并返回
* @param tokenValue token值
* @return session会话
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
* @param tokenValue Token值
* @return Session对象
*/
public static SaSession getTokenSessionByToken(String tokenValue) {
return stpLogic.getTokenSessionByToken(tokenValue);
}
/**
* 获取当前token的专属-session,如果session尚未创建,则新建并返回
* @return session会话
* 获取当前Token-Session,如果Session尚未创建,则新建并返回
* @return Session对象
*/
public static SaSession getTokenSession() {
return stpLogic.getTokenSession();
@@ -320,7 +284,7 @@ public class StpUtil {
// =================== [临时过期] 验证相关 ===================
/**
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
*/
public static void checkActivityTimeout() {
@@ -376,7 +340,7 @@ public class StpUtil {
// =================== 角色验证操作 ===================
/**
* 指定账号id是否含有角色标识, 返回true或false
* 指定账号id是否含有角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
@@ -468,7 +432,7 @@ public class StpUtil {
// =================== id 反查token 相关操作 ===================
/**
* 获取指定loginId的tokenValue
* 获取指定账号id的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
@@ -479,7 +443,7 @@ public class StpUtil {
}
/**
* 获取指定loginId指定设备端的tokenValue
* 获取指定账号id指定设备端的tokenValue
* <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
* 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
* @param loginId 账号id
@@ -490,8 +454,8 @@ public class StpUtil {
return stpLogic.getTokenValueByLoginId(loginId, device);
}
/**
* 获取指定loginId的tokenValue集合
/**
* 获取指定账号id的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
@@ -499,8 +463,8 @@ public class StpUtil {
return stpLogic.getTokenValueListByLoginId(loginId);
}
/**
* 获取指定loginId指定设备端的tokenValue集合
/**
* 获取指定账号id指定设备端的tokenValue 集合
* @param loginId 账号id
* @param device 设备标识
* @return 此loginId的所有相关token
@@ -510,7 +474,7 @@ public class StpUtil {
}
/**
* 返回当前token的登录设备
* 返回当前会话的登录设备
* @return 当前令牌的登录设备
*/
public static String getLoginDevice() {
@@ -521,7 +485,7 @@ public class StpUtil {
// =================== 会话管理 ===================
/**
* 根据条件查询token
* 根据条件查询Token
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
@@ -543,7 +507,7 @@ public class StpUtil {
}
/**
* 根据条件查询token专属Session的Id
* 根据条件查询Token专属Session的Id
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
@@ -552,12 +516,51 @@ public class StpUtil {
public static List<String> searchTokenSessionId(String keyword, int start, int size) {
return stpLogic.searchTokenSessionId(keyword, start, size);
}
// ------------------- 账号封禁 -------------------
/**
* 封禁指定账号
* <p> 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
*/
public static void disable(Object loginId, long disableTime) {
stpLogic.disable(loginId, disableTime);
}
/**
* 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
* @param loginId 账号id
* @return see note
*/
public static boolean isDisable(Object loginId) {
return stpLogic.isDisable(loginId);
}
/**
* 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
* @param loginId 账号id
* @return see note
*/
public static long getDisableTime(Object loginId) {
return stpLogic.getDisableTime(loginId);
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public static void untieDisable(Object loginId) {
stpLogic.untieDisable(loginId);
}
// =================== 身份切换 ===================
/**
* 临时切换身份为指定loginId
* 临时切换身份为指定账号id
* @param loginId 指定loginId
*/
public static void switchTo(Object loginId) {
@@ -580,8 +583,8 @@ public class StpUtil {
}
/**
* 在一个代码段里方法内,临时切换身份为指定loginId
* @param loginId 指定loginId
* 在一个代码段里方法内,临时切换身份为指定账号id
* @param loginId 指定账号id
* @param function 要执行的方法
*/
public static void switchTo(Object loginId, SaFunction function) {
@@ -589,6 +592,47 @@ public class StpUtil {
}
// ------------------- 二级认证 -------------------
/**
* 在当前会话 开启二级认证
* @param safeTime 维持时间 (单位: 秒)
*/
public static void openSafe(long safeTime) {
stpLogic.openSafe(safeTime);
}
/**
* 当前会话 是否处于二级认证时间内
* @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
*/
public static boolean isSafe() {
return stpLogic.isSafe();
}
/**
* 检查当前会话是否已通过二级认证,如未通过则抛出异常
*/
public static void checkSafe() {
stpLogic.checkSafe();
}
/**
* 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
* @return 剩余有效时间
*/
public static long getSafeTime() {
return stpLogic.getSafeTime();
}
/**
* 在当前会话 结束二级认证
*/
public static void closeSafe() {
stpLogic.closeSafe();
}
// =================== 历史API,兼容旧版本 ===================
/**
@@ -1,5 +1,8 @@
package cn.dev33.satoken.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -9,6 +12,8 @@ import java.util.List;
import java.util.Random;
import java.util.regex.Pattern;
import cn.dev33.satoken.exception.SaTokenException;
/**
* Sa-Token 内部工具类
*
@@ -18,7 +23,7 @@ import java.util.regex.Pattern;
public class SaFoxUtil {
/**
* 打印 sa-token 版本字符画
* 打印 Sa-Token 版本字符画
*/
public static void printSaToken() {
String str = "____ ____ ___ ____ _ _ ____ _ _ \r\n" + "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
@@ -49,11 +54,11 @@ public class SaFoxUtil {
}
/**
* 指定字符串是否为null或者空字符串
* @param str 指定字符串
* 指定元素是否为null或者空字符串
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isEmpty(String str) {
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
@@ -147,7 +152,6 @@ public class SaFoxUtil {
return Pattern.matches(patt.replaceAll("\\*", ".*"), str);
}
/**
* 将指定值转化为指定类型
* @param <T> 泛型
@@ -186,5 +190,209 @@ public class SaFoxUtil {
return (T)obj3;
}
/**
* 在url上拼接上kv参数并返回
* @param url url
* @param parameStr 参数, 例如 id=1001
* @return 拼接后的url字符串
*/
public static String joinParam(String url, String parameStr) {
// 如果参数为空, 直接返回
if(parameStr == null || parameStr.length() == 0) {
return url;
}
if(url == null) {
url = "";
}
int index = url.lastIndexOf('?');
// ? 不存在
if(index == -1) {
return url + '?' + parameStr;
}
// ? 是最后一位
if(index == url.length() - 1) {
return url + parameStr;
}
// ? 是其中一位
if(index > -1 && index < url.length() - 1) {
String separatorChar = "&";
// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) {
return url + separatorChar + parameStr;
} else {
return url + parameStr;
}
}
// 正常情况下, 代码不可能执行到此
return url;
}
/**
* 在url上拼接上kv参数并返回
* @param url url
* @param key 参数名称
* @param value 参数值
* @return 拼接后的url字符串
*/
public static String joinParam(String url, String key, Object value) {
// 如果参数为空, 直接返回
if(isEmpty(url) || isEmpty(key) || isEmpty(value)) {
return url;
}
return joinParam(url, key + "=" + value);
}
/**
* 在url上拼接锚参数
* @param url url
* @param parameStr 参数, 例如 id=1001
* @return 拼接后的url字符串
*/
public static String joinSharpParam(String url, String parameStr) {
// 如果参数为空, 直接返回
if(parameStr == null || parameStr.length() == 0) {
return url;
}
if(url == null) {
url = "";
}
int index = url.lastIndexOf('#');
// ? 不存在
if(index == -1) {
return url + '#' + parameStr;
}
// ? 是最后一位
if(index == url.length() - 1) {
return url + parameStr;
}
// ? 是其中一位
if(index > -1 && index < url.length() - 1) {
String separatorChar = "&";
// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) {
return url + separatorChar + parameStr;
} else {
return url + parameStr;
}
}
// 正常情况下, 代码不可能执行到此
return url;
}
/**
* 在url上拼接锚参数
* @param url url
* @param key 参数名称
* @param value 参数值
* @return 拼接后的url字符串
*/
public static String joinSharpParam(String url, String key, Object value) {
// 如果参数为空, 直接返回
if(isEmpty(url) || isEmpty(key) || isEmpty(value)) {
return url;
}
return joinSharpParam(url, key + "=" + value);
}
/**
* 将数组的所有元素使用逗号拼接在一起
* @param arr 数组
* @return 字符串,例: a,b,c
*/
public static String arrayJoin(String[] arr) {
if(arr == null) {
return "";
}
String str = "";
for (int i = 0; i < arr.length; i++) {
str += arr[i];
if(i != arr.length - 1) {
str += ",";
}
}
return str;
}
/**
* 验证URL的正则表达式
*/
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
/**
* 使用正则表达式判断一个字符串是否为URL
* @param str 字符串
* @return 拼接后的url字符串
*/
public static boolean isUrl(String str) {
if(str == null) {
return false;
}
return str.toLowerCase().matches(URL_REGEX);
}
/**
* URL编码
* @param url see note
* @return see note
*/
public static String encodeUrl(String url) {
try {
return URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new SaTokenException(e);
}
}
/**
* URL解码
* @param url see note
* @return see note
*/
public static String decoderUrl(String url) {
try {
return URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new SaTokenException(e);
}
}
/**
* 将指定字符串按照逗号分隔符转化为字符串集合
* @param str 字符串
* @return 分割后的字符串集合
*/
public static List<String> convertStringToList(String str) {
List<String> list = new ArrayList<String>();
if(isEmpty(str)) {
return list;
}
String[] arr = str.split(",");
for (String s : arr) {
s = s.trim();
if(isEmpty(s) == false) {
list.add(s);
}
}
return list;
}
/**
* 将指定集合按照逗号连接成一个字符串
* @param list 集合
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
return "";
}
String str = "";
for (int i = 0; i < list.size(); i++) {
str += list.get(i);
if(i != list.size() - 1) {
str += ",";
}
}
return str;
}
}
@@ -0,0 +1,142 @@
package cn.dev33.satoken.util;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 对Ajax请求返回Json格式数据的简易封装 <br>
* 所有预留字段:<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* @author kong
*
*/
public class SaResult extends LinkedHashMap<String, Object> implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200;
public static final int CODE_ERROR = 500;
public SaResult(int code, String msg, Object data) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
}
/**
* 获取code
* @return code
*/
public Integer getCode() {
return (Integer)this.get("code");
}
/**
* 获取msg
* @return msg
*/
public String getMsg() {
return (String)this.get("msg");
}
/**
* 获取data
* @return data
*/
public Object getData() {
return (Object)this.get("data");
}
/**
* 给code赋值,连缀风格
* @param code code
* @return 对象自身
*/
public SaResult setCode(int code) {
this.put("code", code);
return this;
}
/**
* 给msg赋值,连缀风格
* @param msg msg
* @return 对象自身
*/
public SaResult setMsg(String msg) {
this.put("msg", msg);
return this;
}
/**
* 给data赋值,连缀风格
* @param data data
* @return 对象自身
*/
public SaResult setData(Object data) {
this.put("data", data);
return this;
}
/**
* 写入一个值 自定义key, 连缀风格
* @param key key
* @param data data
* @return 对象自身
*/
public SaResult set(String key, Object data) {
this.put(key, data);
return this;
}
/**
* 写入一个Map, 连缀风格
* @param map map
* @return 对象自身
*/
public SaResult setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
return this;
}
// ============================ 构建 ==================================
// 构建成功
public static SaResult ok() {
return new SaResult(CODE_SUCCESS, "ok", null);
}
public static SaResult ok(String msg) {
return new SaResult(CODE_SUCCESS, msg, null);
}
public static SaResult data(Object data) {
return new SaResult(CODE_SUCCESS, "ok", data);
}
// 构建失败
public static SaResult error() {
return new SaResult(CODE_ERROR, "error", null);
}
public static SaResult error(String msg) {
return new SaResult(CODE_ERROR, msg, null);
}
// 构建指定状态码
public static SaResult get(int code, String msg, Object data) {
return new SaResult(code, msg, data);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": \"" + this.getData() + "\""
+ "}";
}
}
@@ -1,7 +1,7 @@
package cn.dev33.satoken.util;
/**
* sa-token常量类
* Sa-Token常量类
* @author kong
*
*/
@@ -11,17 +11,17 @@ public class SaTokenConsts {
// =================== sa-token版本信息 ===================
/**
* sa-token 当前版本号
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.20.0";
public static final String VERSION_NO = "v1.24.0";
/**
* sa-token 开源地址
* Sa-Token 开源地址
*/
public static final String GITHUB_URL = "https://github.com/dromara/sa-token";
/**
* sa-token 开发文档地址
* Sa-Token 开发文档地址
*/
public static final String DEV_DOC_URL = "http://sa-token.dev33.cn";
@@ -47,36 +47,41 @@ public class SaTokenConsts {
*/
public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_";
/**
* 常量key标记: 在进行Token二级验证时使用的key
*/
public static final String SAFE_AUTH_SAVE_KEY = "SAFE_AUTH_SAVE_KEY_";
// =================== token-style 相关 ===================
/**
* token风格: uuid
* Token风格: uuid
*/
public static final String TOKEN_STYLE_UUID = "uuid";
/**
* token风格: 简单uuid (不带下划线)
* Token风格: 简单uuid (不带下划线)
*/
public static final String TOKEN_STYLE_SIMPLE_UUID = "simple-uuid";
/**
* token风格: 32位随机字符串
* Token风格: 32位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_32 = "random-32";
/**
* token风格: 64位随机字符串
* Token风格: 64位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_64 = "random-64";
/**
* token风格: 128位随机字符串
* Token风格: 128位随机字符串
*/
public static final String TOKEN_STYLE_RANDOM_128 = "random-128";
/**
* token风格: tik风格 (2_14_16)
* Token风格: tik风格 (2_14_16)
*/
public static final String TOKEN_STYLE_TIK = "tik";
@@ -84,7 +89,7 @@ public class SaTokenConsts {
// =================== 其它 ===================
/**
* 连接token前缀和token值的字符
* 连接Token前缀和Token值的字符
*/
public static final String TOKEN_CONNECTOR_CHAT = " ";
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath
@@ -0,0 +1,71 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-alone-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 整合Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,21 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token整合SpringBoot 示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenAloneRedisApplication {
public static void main(String[] args) throws ClassNotFoundException {
SpringApplication.run(SaTokenAloneRedisApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -0,0 +1,162 @@
package com.pj.test;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值,连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值,连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok,小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}
@@ -0,0 +1,39 @@
package com.pj.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
@Autowired
StringRedisTemplate stringRedisTemplate;
// 测试Sa-Token缓存, 浏览器访问: http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
System.out.println("--------------- 测试Sa-Token缓存");
StpUtil.login(id);
return AjaxJson.getSuccess();
}
// 测试业务缓存 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
System.out.println("--------------- 测试业务缓存");
stringRedisTemplate.opsForValue().set("hello", "Hello World");
return AjaxJson.getSuccess();
}
}
@@ -0,0 +1,63 @@
# 端口
server:
port: 8081
# Sa-Token配置
sa-token:
# Token名称 (同时也是cookie名称)
token-name: satoken
# Token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# Token风格
token-style: uuid
# 配置Sa-Token单独使用的Redis连接
alone-redis:
# Redis数据库索引(默认为0
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
spring:
# 配置业务使用的Redis连接
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 10ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+7 -2
View File
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
@@ -79,8 +79,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<!-- <version>2.3.1</version> -->
</dependency>
</dependencies>
</project>
</project>
@@ -110,7 +110,10 @@ public class SaTokenJwtUtil {
warn += "-------------------------------------";
System.err.println(warn);
}
// 提前调用一下方法,促使其属性初始化
StpUtil.getLoginType();
// 修改默认实现
StpUtil.stpLogic = new StpLogic("login") {
@@ -60,9 +60,9 @@ public class TestJwtController {
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
System.out.println("测试取值name" + StpUtil.getSession().get("name"));
StpUtil.getSession().set("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().get("name"));
System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
@@ -2,21 +2,21 @@
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
spring:
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# redis配置
redis:
# Redis数据库索引(默认为0
@@ -17,31 +17,44 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.15.0.RELEASE</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- thymeleaf 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- OkHttps网络请求库: http://okhttps.ejlchina.com/ -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>2.4.5</version>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>3.1.1</version>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动
* 启动Sa-OAuth2 ClientServer端
* @author kong
*/
@SpringBootApplication
@@ -12,7 +12,14 @@ public class SaOAuth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ClientApplication.class, args);
System.out.println("\n客户端启动成功,访问: http://localhost:8002/login.html");
System.out.println("\nSa-Token-OAuth Client端启动成功\n\n" + str);
}
static String str = "-------------------- Sa-Token-OAuth2 示例 --------------------\n\n" +
"首先在host文件 (C:\\windows\\system32\\drivers\\etc\\hosts) 添加以下内容: \r\n" +
" 127.0.0.1 sa-oauth-server.com \r\n" +
" 127.0.0.1 sa-oauth-client.com \r\n" +
"再从浏览器访问:\r\n" +
" http://sa-oauth-client.com:8002";
}
@@ -1,76 +0,0 @@
package com.pj.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ejlchina.okhttps.OkHttps;
import com.pj.utils.AjaxJson;
import com.pj.utils.SoMap;
import cn.dev33.satoken.stp.StpUtil;
/**
* 登录注册Controller
* @author kong
*/
@RestController
public class ClientAccController {
// 返回当前登录者的账号id, 如果未登录, 返回null
@RequestMapping("/getLoginInfo")
public AjaxJson getLoginInfo() {
Object loginId = StpUtil.getLoginIdDefaultNull();
return AjaxJson.getSuccessData(loginId);
}
// 注销登录
@RequestMapping("/logout")
public AjaxJson logout() {
StpUtil.logout();
return AjaxJson.getSuccess();
}
// 根据code码进行登录
@RequestMapping("/doCodeLogin")
public AjaxJson doCodeLogin(String code) {
System.out.println("------------------ 成功进入请求 ------------------");
// 请求服务提供方接口地址,获取 access_token 以及其他信息
// 携带三个关键参数: code、client_id、client_secret
String str = OkHttps.sync("http://localhost:8001/oauth2/getAccessToken")
.addBodyPara("code", code)
.addBodyPara("client_id", "123123123")
.addBodyPara("client_secret", "aaaa-bbbb-cccc-dddd-eeee")
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return AjaxJson.getError(so.getString("msg"));
}
// 根据openid获取其对应的userId
String openid = so.getString("openid");
long userId = getUserIdByOpenid(openid);
// 登录并返回账号信息
StpUtil.setLoginId(userId);
return AjaxJson.getSuccessData(userId).set("openid", openid);
}
// ------------ 模拟方法 ------------------
// 模拟方法:根据openid获取userId
private long getUserIdByOpenid(String openid) {
// 此方法仅做模拟,实际开发要根据具体业务逻辑来获取userId
return 10001;
}
}
@@ -1,59 +0,0 @@
package com.pj.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.utils.AjaxJson;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常拦截
* <p> @ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin")
* @author kong
*
*/
@ControllerAdvice
public class ExceptionHandle {
/** 全局异常拦截 */
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e) {
// 打印堆栈,以供调试
e.printStackTrace();
// 记录日志信息
AjaxJson aj = null;
// ------------- 判断异常类型,提供个性化提示信息
// 如果是未登录异常
if(e instanceof NotLoginException){
aj = AjaxJson.getNotLogin();
}
// 如果是角色异常
else if(e instanceof NotRoleException) {
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getCode());
}
// 如果是权限异常
else if(e instanceof NotPermissionException) {
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
}
// 普通异常输出:500 + 异常信息
else {
aj = AjaxJson.getError(e.getMessage());
}
// 返回到前台
return aj;
}
}
@@ -0,0 +1,188 @@
package com.pj.oauth2;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.ejlchina.okhttps.OkHttps;
import com.pj.utils.SoMap;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Client端 控制器
* @author kong
*/
@RestController
public class SaOAuthClientController {
// 相关参数配置
private String clientId = "1001"; // 应用id
private String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
private String serverUrl = "http://sa-oauth-server.com:8001"; // 服务端接口
// 进入首页
@RequestMapping("/")
public Object index(HttpServletRequest request) {
request.setAttribute("uid", StpUtil.getLoginIdDefaultNull());
return new ModelAndView("index.html");
}
// 根据Code码进行登录,获取 Access-Token 和 openid
@RequestMapping("/codeLogin")
public SaResult codeLogin(String code) {
// 调用Server端接口,获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code")
.addBodyPara("code", code)
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
}
// 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh")
public SaResult refresh(String refreshToken) {
// 调用Server端接口,通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token")
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.addBodyPara("refresh_token", refreshToken)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Access-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 模式三:密码式-授权登录
@RequestMapping("/passwordLogin")
public SaResult passwordLogin(String username, String password) {
// 模式三:密码式-授权登录
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "password")
.addBodyPara("client_id", clientId)
.addBodyPara("username", username)
.addBodyPara("password", password)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
}
// 模式四:获取应用的 Client-Token
@RequestMapping("/clientToken")
public SaResult clientToken() {
// 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials")
.addBodyPara("client_id", clientId)
.addBodyPara("client_secret", clientSecret)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Client-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 注销登录
@RequestMapping("/logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
// 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息
@RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) {
// 调用Server端接口,查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken)
.post()
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=获取到的资源 )
SoMap data = so.getMap("data");
return SaResult.data(data);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
// ------------ 模拟方法 ------------------
// 模拟方法:根据openid获取userId
private long getUserIdByOpenid(String openid) {
// 此方法仅做模拟,实际开发要根据具体业务逻辑来获取userId
return 10001;
}
}
@@ -1,223 +0,0 @@
package com.pj.utils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ajax请求返回Json格式数据的封装 <br>
* 所有预留字段:<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* pageNo=当前页 <br>
* pageSize=页大小 <br>
* startIndex=起始索引 <br>
* dataCount=数据总数 <br>
* pageCount=分页总数 <br>
* <p> 返回范例:</p>
* <pre>
{
"code": 200, // 成功时=200, 失败时=500 msg=失败原因
"msg": "ok",
"data": {}
}
</pre>
*/
public class AjaxJson extends LinkedHashMap<String, Object> implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
// ============================ 写值取值 ==================================
/** 给code赋值,连缀风格 */
public AjaxJson setCode(int code) {
this.put("code", code);
return this;
}
/** 返回code */
public Integer getCode() {
return (Integer)this.get("code");
}
/** 给msg赋值,连缀风格 */
public AjaxJson setMsg(String msg) {
this.put("msg", msg);
return this;
}
/** 获取msg */
public String getMsg() {
return (String)this.get("msg");
}
/** 给data赋值,连缀风格 */
public AjaxJson setData(Object data) {
this.put("data", data);
return this;
}
/** 获取data */
public String getData() {
return (String)this.get("data");
}
/** 将data还原为指定类型并返回 */
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) this.getData();
}
/** 给dataCount(数据总数)赋值,连缀风格 */
public AjaxJson setDataCount(Long dataCount) {
this.put("dataCount", dataCount);
// 如果提供了数据总数,则尝试计算page信息
if(dataCount != null && dataCount >= 0) {
// 如果:已有page信息
if(get("pageNo") != null) {
this.initPageInfo();
}
// // 或者:是JavaWeb环境
// else if(SoMap.isJavaWeb()) {
// SoMap so = SoMap.getRequestSoMap();
// this.setPageNoAndSize(so.getKeyPageNo(), so.getKeyPageSize());
// this.initPageInfo();
// }
}
return this;
}
/** 获取dataCount(数据总数) */
public String getDataCount() {
return (String)this.get("dataCount");
}
/** 设置pageNo 和 pageSize,并计算出startIndex于pageCount */
public AjaxJson setPageNoAndSize(long pageNo, long pageSize) {
this.put("pageNo", pageNo);
this.put("pageSize", pageSize);
return this;
}
/** 根据 pageSize dataCount,计算startIndex 与 pageCount */
public AjaxJson initPageInfo() {
long pageNo = (long)this.get("pageNo");
long pageSize = (long)this.get("pageSize");
long dataCount = (long)this.get("dataCount");
this.set("startIndex", (pageNo - 1) * pageSize);
long pc = dataCount / pageSize;
this.set("pageCount", (dataCount % pageSize == 0 ? pc : pc + 1));
return this;
}
/** 写入一个值 自定义key, 连缀风格 */
public AjaxJson set(String key, Object data) {
this.put(key, data);
return this;
}
/** 写入一个Map, 连缀风格 */
public AjaxJson setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
return this;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
this.setDataCount(dataCount == null ? -1 : dataCount);
}
/** 返回成功 */
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
/** 返回失败 */
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
/** 返回警告 */
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
/** 返回未登录 */
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
/** 返回没有权限的 */
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
/** 返回一个自定义状态码的 */
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
/** 返回分页和数据的 */
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
/** 返回, 根据受影响行数的(大于0=ok,小于0=error) */
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
/** 返回,根据布尔值来确定最终结果的 (true=okfalse=error) */
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
// // 历史版本遗留代码
// public int code; // 状态码
// public String msg; // 描述信息
// public Object data; // 携带对象
// public Long dataCount; // 数据总数,用于分页
}
@@ -33,12 +33,10 @@ public class SoMap extends LinkedHashMap<String, Object> {
public SoMap() {
}
/** 以下元素会在isNull函数中被判定为Null */
public static final Object[] NULL_ELEMENT_ARRAY = {null, ""};
public static final List<Object> NULL_ELEMENT_LIST;
static {
NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY);
}
@@ -144,6 +142,22 @@ public class SoMap extends LinkedHashMap<String, Object> {
return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss");
}
/** 转为Map并返回 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SoMap getMap(String key) {
Object value = get(key);
if(value == null) {
return SoMap.getSoMap();
}
if(value instanceof Map) {
return SoMap.getSoMap((Map)value);
}
if(value instanceof String) {
return SoMap.getSoMap().setJsonString((String)value);
}
throw new RuntimeException("值无法转化为SoMap: " + value);
}
/** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */
@SuppressWarnings("unchecked")
public List<Object> getList(String key) {
@@ -1,14 +1,7 @@
server:
port: 8002
spring:
# 静态文件路径映射
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
# static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-client\src\main\resources\static\
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-client
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-client
@@ -1,127 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>客户端-登录页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 500px; margin: 50px auto;}
</style>
</head>
<body>
<div class="login-box">
<h2>客户端-登录页</h2> <br>
<div>
当前是否登录: <span class="login-info"></span>
<button onclick="logout()">注销登录</button>
</div>
<br/>
点此按钮开始使用OAuth2.0开放平台快捷登录:
<button onclick="requestOAuth2()">快捷登录</button>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 获取登录信息
var currUserId = null;
function getLoginInfo() {
$.ajax({
url: '/getLoginInfo',
data: {},
dataType: 'json',
success: function(res) {
if(res.data == null) {
$(".login-info").html('<b style="color: red;">未登录</b>');
// 如果当前未登录,并且此时url中有code参数, 则尝试使用code码进行登录
var code = getParam('code', null);
if(code != null) {
doCodeLogin();
}
} else {
$(".login-info").html('<b style="color: green;">已登录, userId=' + res.data + '</b>');
currUserId = res.data;
}
},
error: function(e, ee, eee) {
console.log('error');
alert(eee);
}
});
}
getLoginInfo();
// 注销登录
function logout() {
$.ajax({
url: '/logout',
dataType: 'json',
success: function(res) {
alert('注销成功');
location.href = "./login.html";
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 请求OAuth2授权
function requestOAuth2() {
// 如果当前已经登录,则必须先退出
if(currUserId != null) {
alert('当前已经登录, 请先注销');
return;
}
// 拼接地址
var url = "http://localhost:8001/oauth2/authorize" +
"?client_id=123123123" + // 应用id
"&scope=userinfo" + // 授权范围
// "&redirect_uri=" + encodeURIComponent(location.href) // 重定向地址
"&redirect_uri=" + encodeURIComponent("http://localhost:8002/login.html") // 重定向地址
"&response_type=code" + // 返回格式
"&state=123456789"; // 授权范围
console.log(url);
location.href = url;
}
// 使用code进行 (从url中获取code码进行登录)
function doCodeLogin() {
var code = getParam('code');
$.ajax({
url: '/doCodeLogin',
data: {
code: code
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
alert('OAuth2登录成功');
getLoginInfo(); // 刷新user信息
} else {
alert("登录失败:" + res.msg);
}
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>
@@ -0,0 +1,258 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-Client端-测试页</title>
<style type="text/css">
body{background-color: #D0D9E0;}
*{margin: 0px; padding: 0px;}
.login-box{max-width: 1000px; margin: 30px auto; padding: 1em;}
.info{line-height: 30px;}
.btn-box{margin-top: 10px; margin-bottom: 15px;}
.btn-box a{margin-right: 10px;}
.btn-box a:hover{text-decoration:underline !important;}
.login-box input{line-height: 25px; margin-bottom: 10px; padding-left: 5px;}
.login-box button{padding: 5px 15px; margin-top: 20px; cursor: pointer; }
.login-box a{text-decoration: none;}
.pst{color: #666; margin-top: 15px;}
.ps{color: #666; margin-left: 10px;}
.login-box code{display: block; background-color: #F5F2F0; border: 1px #ccc solid; color: #600; padding: 15px; margin-top: 5px; border-radius: 2px; }
.info b,.info span{color: green;}
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-Client端-测试页</h2> <br>
<div class="info">
<div>当前账号id
<b class="uid" th:utext="${uid}"></b>
</div>
<div>当前Openid <span class="openid"></span></div>
<div>当前Access-Token <span class="access_token"></span></div>
<div>当前Refresh-Token <span class="refresh_token"></span></div>
<div>当前令牌包含Scope <span class="scope"></span></div>
<div>当前Client-Token <span class="client_token"></span></div>
</div>
<div class="btn-box">
<a href="javascript:logout();">注销</a>
<a href="/">回到首页</a>
</div>
<hr><br>
<h3>模式一:授权码(Authorization Code</h3>
<p class="pst">授权码:OAuth2.0标准授权流程,先 (重定向) 获取Code授权码,再 (Rest API) 获取 Access-Token 和 Openid </p>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<button>点我开始授权登录(静默授权)</button>
</a>
<span class="ps">当请求链接不包含scope权限时,将无需用户手动确认,做到静默授权,当然此时我们也只能获取openid</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>授权登录(显式授权)</button>
</a>
<span class="ps">当请求链接包含具体的scope权限时,将需要用户手动确认,此时我们除了openid以外还可以获取更多的资源</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<button onclick="refreshToken()">刷新令牌</button>
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废</span>
<code>http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<button onclick="getUserinfo()">获取账号信息</button>
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) </span>
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
<br>
<h3>模式二:隐藏式(Implicit</h3>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>隐藏式</button>
</a>
<span class="ps">越过授权码的步骤,直接返回token到前端页面( 格式:http//:domain.com#token=xxxx-xxxx </span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<br>
<h3>模式三:密码式(Password</h3>
<p class="pst">在下面输入Server端的用户名和密码,使用密码式进行 OAuth2 授权登录</p>
账号:<input name="username">
密码:<input name="password">
<button onclick="passwordLogin()">登录</button>
<code>http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&username={value}&password={value}</code>
<br>
<h3>模式四:凭证式(Client Credentials</h3>
<p class="pst">以上三种模式获取的都是用户的 Access-Token,代表用户对第三方应用的授权,在OAuth2.0中还有一种针对 Client级别的授权,
即:Client-Token,代表应用自身的资源授权</p>
<p class="pst">Client-Token具有延迟作废特性,即:在每次获取最新Client-Token的时候,旧Client-Token不会立即过期,而是作为Past-Token再次
储存起来,资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”,
保证了服务的高可用</p>
<button onclick="getClientToken()">获取应用Client-Token</button>
<code>http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<br><br>
<span>更多资料请参考 Sa-Token 官方文档地址:</span>
<a href="http://sa-token.dev33.cn/">http://sa-token.dev33.cn/</a>
<div style="height: 200px;"></div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 根据code授权码进行登录
function doLogin(code) {
$.ajax({
url: '/codeLogin?code=' + code,
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
var code = getParam('code');
if(code) {
doLogin(code);
}
// 根据 Refresh-Token 去刷新 Access-Token
function refreshToken() {
var refreshToken = $('.refresh_token').text();
if(refreshToken == '') {
return layer.alert('您还没有获取 Refresh-Token ,请先授权登录');
}
$.ajax({
url: '/refresh?refreshToken=' + refreshToken,
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 模式三:密码式-授权登录
function passwordLogin() {
$.ajax({
url: '/passwordLogin',
data: {
username: $('[name=username]').val(),
password: $('[name=password]').val()
},
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('登录成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 模式四:获取应用的 Client-Token
function getClientToken () {
$.ajax({
url: '/clientToken',
dataType: 'json',
success: function(res) {
console.log('返回:', res);
if(res.code == 200) {
setInfo(res.data);
layer.msg('获取成功!');
} else {
layer.msg(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息
function getUserinfo() {
var accessToken = $('.access_token').text();
if(accessToken == '') {
return layer.alert('您还没有获取 Access-Token ,请先授权登录');
}
$.ajax({
url: '/getUserinfo',
data: {accessToken: accessToken},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
layer.alert(JSON.stringify(res.data));
} else {
layer.alert(res.msg);
}
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 注销
function logout() {
$.ajax({
url: '/logout',
dataType: 'json',
success: function(res) {
location.href = '/';
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}
// 写入数据
function setInfo(info) {
console.log('info', info);
for (var key in info) {
$('.' + key).text(info[key]);
}
if($('.uid').text() == '') {
$('.uid').html('<b style="color: #E00;">未登录</b>')
}
}
setInfo({});
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>
@@ -17,33 +17,33 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.15.0.RELEASE</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- sa-token 实现 oauth2.0 -->
<!-- Sa-Token-OAuth2.0 模块 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>1.15.0-alpha</version>
<version>${sa-token-version}</version>
</dependency>
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<!-- Sa-Token整合Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
@@ -51,7 +51,20 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
</dependency>
<!-- thymeleaf 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- ConfigurationProperties -->
<dependency>
@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动
* 启动Sa-OAuth2 Server端
* @author kong
*/
@SpringBootApplication
@@ -12,7 +12,7 @@ public class SaOAuth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ServerApplication.class, args);
System.out.println("\n服务端启动成功");
System.out.println("\nSa-Token-OAuth Server端启动成功");
}
}
@@ -1,59 +0,0 @@
package com.pj.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.utils.AjaxJson;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常拦截
* <p> @ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin")
* @author kong
*
*/
@ControllerAdvice
public class ExceptionHandle {
/** 全局异常拦截 */
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e) {
// 打印堆栈,以供调试
e.printStackTrace();
// 记录日志信息
AjaxJson aj = null;
// ------------- 判断异常类型,提供个性化提示信息
// 如果是未登录异常
if(e instanceof NotLoginException){
aj = AjaxJson.getNotLogin();
}
// 如果是角色异常
else if(e instanceof NotRoleException) {
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getCode());
}
// 如果是权限异常
else if(e instanceof NotPermissionException) {
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
}
// 普通异常输出:500 + 异常信息
else {
aj = AjaxJson.getError(e.getMessage());
}
// 返回到前台
return aj;
}
}
@@ -1,147 +0,0 @@
package com.pj.controller;
import java.io.IOException;
import java.net.URLDecoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.utils.AjaxJson;
import com.pj.utils.SoMap;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.oauth2.model.AccessTokenModel;
import cn.dev33.satoken.oauth2.model.CodeModel;
import cn.dev33.satoken.oauth2.model.RequestAuthModel;
import cn.dev33.satoken.stp.StpUtil;
@RestController
@RequestMapping("/oauth2/")
public class OAuth2Controller {
// 获取授权码
@RequestMapping("/authorize")
public AjaxJson authorize(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 获取参数
System.out.println("------------------ 成功进入请求 ------------------");
// 如果暂未登录,则先跳转到登录页 (转发)
if(StpUtil.isLogin() == false) {
response.setContentType("text/html");
request.getRequestDispatcher("/login.html").forward(request, response);
return AjaxJson.getSuccess();
}
// 构建Model
RequestAuthModel authModel = new RequestAuthModel()
.setClientId(request.getParameter("client_id")) // 应用id
.setScope(request.getParameter("scope")) // 授权类型
.setLoginId(StpUtil.getLoginIdAsLong()) // 当前登录账号id
.setRedirectUri(URLDecoder.decode(request.getParameter("redirect_uri"), "utf-8")) // 重定向地址
.setResponseType(request.getParameter("response_type")) // 返回类型
.setState(request.getParameter("state")) // 状态值
.checkModel(); // 校验参数完整性
// 生成授权码Model
CodeModel codeModel = SaOAuth2Util.generateCode(authModel);
// 打印调试
System.out.println("应用id=" + authModel.getClientId() + "请求授权,授权类型=" + authModel.getResponseType());
System.out.println("重定向地址:" + authModel.getRedirectUri());
System.out.println("拼接完成的redirect_uri: " + codeModel.getRedirectUri());
System.out.println("如果用户拒绝授权,则重定向至: " + codeModel.getRejectUri());
// 如果请求的权限用户已经确认,直接开始重定向授权
if(codeModel.getIsConfirm() == true) {
response.sendRedirect(codeModel.getRedirectUri());
} else {
// 如果请求的权限用户尚未确认,则进入到确定页
request.setAttribute("name", "sdd");
response.sendRedirect("/auth.html?code=" + codeModel.getCode());
}
return AjaxJson.getSuccess();
}
// 根据授权码获取应用信息
@RequestMapping("/getCodeInfo")
public AjaxJson getCodeInfo(String code) {
// 获取codeModel
CodeModel codeModel = SaOAuth2Util.getCode(code);
System.out.println(code);
System.out.println(codeModel);
// 返回
return AjaxJson.getSuccessData(codeModel);
}
// 确认授权一个授权码
@RequestMapping("/confirm")
public AjaxJson confirm(String code) {
// 获取codeModel
CodeModel codeModel = SaOAuth2Util.getCode(code);
if(codeModel == null) {
return AjaxJson.getError("无效code码");
}
// 此处的判断是为了保证当前账号id 和 创建授权码的账号id一致 才可以进行确认
if(codeModel.getLoginId().toString().equals(StpUtil.getLoginIdAsString()) == false) {
return AjaxJson.getError("暂无权限");
}
// 进行确认
SaOAuth2Util.confirmCode(code);
// 返回ok
return AjaxJson.getSuccess();
}
// 根据授权码等参数,获取 access_token 等信息
@RequestMapping("/getAccessToken")
public SoMap getAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取参数
System.out.println("------------------ 成功进入请求 ------------------");
String code = request.getParameter("code"); // code码
String clientId = request.getParameter("client_id"); // 应用id
String clientSecret = request.getParameter("client_secret"); // 应用秘钥
// 校验参数
SaOAuth2Util.checkCodeIdSecret(code, clientId, clientSecret);
// 生成
CodeModel codeModel = SaOAuth2Util.getCode(code);
AccessTokenModel tokenModel = SaOAuth2Util.generateAccessToken(codeModel);
// 生成AccessToken之后,将授权码立即销毁
SaOAuth2Util.deleteCode(code);
// 返回
return SoMap.getSoMap()
.setModel(tokenModel)
.set("code", 200)
.set("msg", "ok");
}
// 根据access_token返回指定的资源
@RequestMapping("/getResources")
public SoMap getResources(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取信息
String accessToken = request.getParameter("access_token");
Object LoginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("LoginId=" + LoginId);
// 根据LoginId获取相应信息...
// 此处仅做模拟
return new SoMap()
.set("nickname", "shengzhang")
.set("acatar", "xxx")
.set("sex", 1);
}
}
@@ -1,28 +0,0 @@
package com.pj.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.utils.AjaxJson;
import cn.dev33.satoken.stp.StpUtil;
/**
* 服务端登录Controller
* @author kong
*/
@RestController
public class ServerAccController {
// 登录方法
@RequestMapping("/doLogin")
public AjaxJson test(String username, String password) {
System.out.println("------------------ 成功进入请求 ------------------");
if("test".equals(username) && "test".equals(password)) {
StpUtil.setLoginId(10001);
return AjaxJson.getSuccess();
}
return AjaxJson.getError();
}
}
@@ -1,72 +0,0 @@
package com.pj.oauth2;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface;
/**
* 使用oauth2.0 所必须的一些自定义实现
* @author kong
*/
@Component
public class SaOAuth2InterfaceImpl implements SaOAuth2Interface {
/*
* ------ 注意: 以下代码均为示例,真实环境需要根据数据库查询相关信息
*/
// 返回此平台所有权限集合
@Override
public List<String> getAppScopeList() {
return Arrays.asList("userinfo");
}
// 返回指定Client签约的所有Scope集合
@Override
public List<String> getClientScopeList(String clientId) {
return Arrays.asList("userinfo");
}
// 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
@Override
public List<String> getGrantScopeList(Object loginId, String clientId) {
return Arrays.asList();
}
// 返回指定Client允许的回调域名, 多个用逗号隔开, *代表不限制
@Override
public String getClientDomain(String clientId) {
return "*";
}
// 返回指定ClientId的ClientSecret
@Override
public String getClientSecret(String clientId) {
return "aaaa-bbbb-cccc-dddd-eeee";
}
// 根据ClientId和LoginId返回openid
@Override
public String getOpenid(String clientId, Object loginId) {
return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
}
// 根据ClientId和openid返回LoginId
@Override
public Object getLoginId(String clientId, String openid) {
return 10001;
}
/*
* 以上函数为开发时必须重写实现,其余函数可以按需重写
*/
}
@@ -0,0 +1,92 @@
package com.pj.oauth2;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Handle;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Server端 控制器
* @author kong
*
*/
@RestController
public class SaOAuth2ServerController {
// 处理所有OAuth相关请求
@RequestMapping("/oauth2/*")
public Object request() {
System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl());
return SaOAuth2Handle.serverRequest();
}
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
cfg.
// 未登录的视图
setNotLoginView(()->{
return new ModelAndView("login.html");
}).
// 登录处理函数
setDoLoginHandle((name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
// 授权确认视图
setConfirmView((clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
})
;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
// ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------
// 获取Userinfo信息:昵称、头像、性别等等
@RequestMapping("/oauth2/userinfo")
public SaResult userinfo() {
// 获取 Access-Token 对应的账号id
String accessToken = SaHolder.getRequest().getParamNotNull("access_token");
Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("-------- 此Access-Token对应的账号id: " + loginId);
// 校验 Access-Token 是否具有权限: userinfo
SaOAuth2Util.checkScope(accessToken, "userinfo");
// 模拟账号信息 (真实环境需要查询数据库获取信息)
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("nickname", "shengzhang_");
map.put("avatar", "http://xxx.com/1.jpg");
map.put("age", "18");
map.put("sex", "");
map.put("address", "山东省 青岛市 城阳区");
return SaResult.data(map);
}
}
@@ -1,53 +0,0 @@
package com.pj.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface;
/**
* 利用Spring完成自动装配
*
* @author kong
*
*/
@Component
public class SaOAuth2SpringAutowired {
/**
* 获取OAuth2配置Bean
*
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "spring.sa-token.oauth2")
public SaOAuth2Config getSaOAuth2Config() {
return new SaOAuth2Config();
}
/**
* 注入OAuth2配置Bean
*
* @param saOAuth2Config 配置对象
*/
@Autowired
public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) {
SaOAuth2Manager.setConfig(saOAuth2Config);
}
/**
* 注入OAuth2接口Bean
*
* @param saOAuth2Interface OAuth2接口Bean
*/
@Autowired(required = false)
public void setSaOAuth2Interface(SaOAuth2Interface saOAuth2Interface) {
SaOAuth2Manager.setInterface(saOAuth2Interface);
}
}
@@ -0,0 +1,38 @@
package com.pj.oauth2;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.model.SaClientModel;
/**
* Sa-Token OAuth2.0 整合实现
* @author kong
*/
@Component
public class SaOAuth2TemplateImpl extends SaOAuth2Template {
// 根据 id 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("10001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo");
}
return null;
}
// 根据ClientId 和 LoginId 获取openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此为模拟数据,真实环境需要从数据库查询
return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
}
// -------------- 其它需要重写的函数
}
@@ -1,223 +0,0 @@
package com.pj.utils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ajax请求返回Json格式数据的封装 <br>
* 所有预留字段:<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* pageNo=当前页 <br>
* pageSize=页大小 <br>
* startIndex=起始索引 <br>
* dataCount=数据总数 <br>
* pageCount=分页总数 <br>
* <p> 返回范例:</p>
* <pre>
{
"code": 200, // 成功时=200, 失败时=500 msg=失败原因
"msg": "ok",
"data": {}
}
</pre>
*/
public class AjaxJson extends LinkedHashMap<String, Object> implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
// ============================ 写值取值 ==================================
/** 给code赋值,连缀风格 */
public AjaxJson setCode(int code) {
this.put("code", code);
return this;
}
/** 返回code */
public Integer getCode() {
return (Integer)this.get("code");
}
/** 给msg赋值,连缀风格 */
public AjaxJson setMsg(String msg) {
this.put("msg", msg);
return this;
}
/** 获取msg */
public String getMsg() {
return (String)this.get("msg");
}
/** 给data赋值,连缀风格 */
public AjaxJson setData(Object data) {
this.put("data", data);
return this;
}
/** 获取data */
public String getData() {
return (String)this.get("data");
}
/** 将data还原为指定类型并返回 */
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) this.getData();
}
/** 给dataCount(数据总数)赋值,连缀风格 */
public AjaxJson setDataCount(Long dataCount) {
this.put("dataCount", dataCount);
// 如果提供了数据总数,则尝试计算page信息
if(dataCount != null && dataCount >= 0) {
// 如果:已有page信息
if(get("pageNo") != null) {
this.initPageInfo();
}
// // 或者:是JavaWeb环境
// else if(SoMap.isJavaWeb()) {
// SoMap so = SoMap.getRequestSoMap();
// this.setPageNoAndSize(so.getKeyPageNo(), so.getKeyPageSize());
// this.initPageInfo();
// }
}
return this;
}
/** 获取dataCount(数据总数) */
public String getDataCount() {
return (String)this.get("dataCount");
}
/** 设置pageNo 和 pageSize,并计算出startIndex于pageCount */
public AjaxJson setPageNoAndSize(long pageNo, long pageSize) {
this.put("pageNo", pageNo);
this.put("pageSize", pageSize);
return this;
}
/** 根据 pageSize dataCount,计算startIndex 与 pageCount */
public AjaxJson initPageInfo() {
long pageNo = (long)this.get("pageNo");
long pageSize = (long)this.get("pageSize");
long dataCount = (long)this.get("dataCount");
this.set("startIndex", (pageNo - 1) * pageSize);
long pc = dataCount / pageSize;
this.set("pageCount", (dataCount % pageSize == 0 ? pc : pc + 1));
return this;
}
/** 写入一个值 自定义key, 连缀风格 */
public AjaxJson set(String key, Object data) {
this.put(key, data);
return this;
}
/** 写入一个Map, 连缀风格 */
public AjaxJson setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
return this;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.setCode(code);
this.setMsg(msg);
this.setData(data);
this.setDataCount(dataCount == null ? -1 : dataCount);
}
/** 返回成功 */
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
/** 返回失败 */
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
/** 返回警告 */
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
/** 返回未登录 */
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
/** 返回没有权限的 */
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
/** 返回一个自定义状态码的 */
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
/** 返回分页和数据的 */
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
/** 返回, 根据受影响行数的(大于0=ok,小于0=error) */
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
/** 返回,根据布尔值来确定最终结果的 (true=okfalse=error) */
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
// // 历史版本遗留代码
// public int code; // 状态码
// public String msg; // 描述信息
// public Object data; // 携带对象
// public Long dataCount; // 数据总数,用于分页
}
@@ -1,723 +0,0 @@
package com.pj.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Map< String, Object> 是最常用的一种Map类型,但是它写着麻烦
* <p>所以特封装此类,继承Map,进行一些扩展,可以让Map更灵活使用
* <p>最新:2020-12-10 新增部分构造方法
* @author kong
*/
public class SoMap extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public SoMap() {
}
/** 以下元素会在isNull函数中被判定为Null */
public static final Object[] NULL_ELEMENT_ARRAY = {null, ""};
public static final List<Object> NULL_ELEMENT_LIST;
static {
NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY);
}
// ============================= 读值 =============================
/** 获取一个值 */
@Override
public Object get(Object key) {
if("this".equals(key)) {
return this;
}
return super.get(key);
}
/** 如果为空,则返回默认值 */
public Object get(Object key, Object defaultValue) {
Object value = get(key);
if(valueIsNull(value)) {
return defaultValue;
}
return value;
}
/** 转为String并返回 */
public String getString(String key) {
Object value = get(key);
if(value == null) {
return null;
}
return String.valueOf(value);
}
/** 如果为空,则返回默认值 */
public String getString(String key, String defaultValue) {
Object value = get(key);
if(valueIsNull(value)) {
return defaultValue;
}
return String.valueOf(value);
}
/** 转为int并返回 */
public int getInt(String key) {
Object value = get(key);
if(valueIsNull(value)) {
return 0;
}
return Integer.valueOf(String.valueOf(value));
}
/** 转为int并返回,同时指定默认值 */
public int getInt(String key, int defaultValue) {
Object value = get(key);
if(valueIsNull(value)) {
return defaultValue;
}
return Integer.valueOf(String.valueOf(value));
}
/** 转为long并返回 */
public long getLong(String key) {
Object value = get(key);
if(valueIsNull(value)) {
return 0;
}
return Long.valueOf(String.valueOf(value));
}
/** 转为double并返回 */
public double getDouble(String key) {
Object value = get(key);
if(valueIsNull(value)) {
return 0.0;
}
return Double.valueOf(String.valueOf(value));
}
/** 转为boolean并返回 */
public boolean getBoolean(String key) {
Object value = get(key);
if(valueIsNull(value)) {
return false;
}
return Boolean.valueOf(String.valueOf(value));
}
/** 转为Date并返回,根据自定义格式 */
public Date getDateByFormat(String key, String format) {
try {
return new SimpleDateFormat(format).parse(getString(key));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** 转为Date并返回,根据格式: yyyy-MM-dd */
public Date getDate(String key) {
return getDateByFormat(key, "yyyy-MM-dd");
}
/** 转为Date并返回,根据格式: yyyy-MM-dd HH:mm:ss */
public Date getDateTime(String key) {
return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss");
}
/** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */
@SuppressWarnings("unchecked")
public List<Object> getList(String key) {
Object value = get(key);
List<Object> list = null;
if(value == null || value.equals("")) {
list = new ArrayList<Object>();
}
else if(value instanceof List) {
list = (List<Object>)value;
} else {
list = new ArrayList<Object>();
list.add(value);
}
return list;
}
/** 获取集合 (指定泛型类型) */
public <T> List<T> getList(String key, Class<T> cs) {
List<Object> list = getList(key);
List<T> list2 = new ArrayList<T>();
for (Object obj : list) {
T objC = getValueByClass(obj, cs);
list2.add(objC);
}
return list2;
}
/** 获取集合(逗号分隔式),(指定类型) */
public <T> List<T> getListByComma(String key, Class<T> cs) {
String listStr = getString(key);
if(listStr == null || listStr.equals("")) {
return new ArrayList<>();
}
// 开始转化
String [] arr = listStr.split(",");
List<T> list = new ArrayList<T>();
for (String str : arr) {
if(cs == int.class || cs == Integer.class || cs == long.class || cs == Long.class) {
str = str.trim();
}
T objC = getValueByClass(str, cs);
list.add(objC);
}
return list;
}
/** 根据指定类型从map中取值,返回实体对象 */
public <T> T getModel(Class<T> cs) {
try {
return getModelByObject(cs.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** 从map中取值,塞到一个对象中 */
public <T> T getModelByObject(T obj) {
// 获取类型
Class<?> cs = obj.getClass();
// 循环复制
for (Field field : cs.getDeclaredFields()) {
try {
// 获取对象
Object value = this.get(field.getName());
if(value == null) {
continue;
}
field.setAccessible(true);
Object valueConvert = getValueByClass(value, field.getType());
field.set(obj, valueConvert);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException("属性取值出错:" + field.getName(), e);
}
}
return obj;
}
/**
* 将指定值转化为指定类型并返回
* @param obj
* @param cs
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getValueByClass(Object obj, Class<T> cs) {
String obj2 = String.valueOf(obj);
Object obj3 = null;
if (cs.equals(String.class)) {
obj3 = obj2;
} else if (cs.equals(int.class) || cs.equals(Integer.class)) {
obj3 = Integer.valueOf(obj2);
} else if (cs.equals(long.class) || cs.equals(Long.class)) {
obj3 = Long.valueOf(obj2);
} else if (cs.equals(short.class) || cs.equals(Short.class)) {
obj3 = Short.valueOf(obj2);
} else if (cs.equals(byte.class) || cs.equals(Byte.class)) {
obj3 = Byte.valueOf(obj2);
} else if (cs.equals(float.class) || cs.equals(Float.class)) {
obj3 = Float.valueOf(obj2);
} else if (cs.equals(double.class) || cs.equals(Double.class)) {
obj3 = Double.valueOf(obj2);
} else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) {
obj3 = Boolean.valueOf(obj2);
} else {
obj3 = (T)obj;
}
return (T)obj3;
}
// ============================= 写值 =============================
/**
* 给指定key添加一个默认值(只有在这个key原来无值的情况先才会set进去)
*/
public void setDefaultValue(String key, Object defaultValue) {
if(isNull(key)) {
set(key, defaultValue);
}
}
/** set一个值,连缀风格 */
public SoMap set(String key, Object value) {
// 防止敏感key
if(key.toLowerCase().equals("this")) {
return this;
}
put(key, value);
return this;
}
/** 将一个Map塞进SoMap */
public SoMap setMap(Map<String, ?> map) {
if(map != null) {
for (String key : map.keySet()) {
this.set(key, map.get(key));
}
}
return this;
}
/** 将一个对象解析塞进SoMap */
public SoMap setModel(Object model) {
if(model == null) {
return this;
}
Field[] fields = model.getClass().getDeclaredFields();
for (Field field : fields) {
try{
field.setAccessible(true);
boolean isStatic = Modifier.isStatic(field.getModifiers());
if(!isStatic) {
this.set(field.getName(), field.get(model));
}
}catch (Exception e){
throw new RuntimeException(e);
}
}
return this;
}
/** 将json字符串解析后塞进SoMap */
public SoMap setJsonString(String jsonString) {
try {
@SuppressWarnings("unchecked")
Map<String, Object> map = new ObjectMapper().readValue(jsonString, Map.class);
return this.setMap(map);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
// ============================= 删值 =============================
/** delete一个值,连缀风格 */
public SoMap delete(String key) {
remove(key);
return this;
}
/** 清理所有value为null的字段 */
public SoMap clearNull() {
Iterator<String> iterator = this.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
if(this.isNull(key)) {
iterator.remove();
this.remove(key);
}
}
return this;
}
/** 清理指定key */
public SoMap clearIn(String ...keys) {
List<String> keys2 = Arrays.asList(keys);
Iterator<String> iterator = this.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
if(keys2.contains(key) == true) {
iterator.remove();
this.remove(key);
}
}
return this;
}
/** 清理掉不在列表中的key */
public SoMap clearNotIn(String ...keys) {
List<String> keys2 = Arrays.asList(keys);
Iterator<String> iterator = this.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
if(keys2.contains(key) == false) {
iterator.remove();
this.remove(key);
}
}
return this;
}
/** 清理掉所有key */
public SoMap clearAll() {
clear();
return this;
}
// ============================= 快速构建 =============================
/** 构建一个SoMap并返回 */
public static SoMap getSoMap() {
return new SoMap();
}
/** 构建一个SoMap并返回 */
public static SoMap getSoMap(String key, Object value) {
return new SoMap().set(key, value);
}
/** 构建一个SoMap并返回 */
public static SoMap getSoMap(Map<String, ?> map) {
return new SoMap().setMap(map);
}
/** 将一个对象集合解析成为SoMap */
public static SoMap getSoMapByModel(Object model) {
return SoMap.getSoMap().setModel(model);
}
/** 将一个对象集合解析成为SoMap集合 */
public static List<SoMap> getSoMapByList(List<?> list) {
List<SoMap> listMap = new ArrayList<SoMap>();
for (Object model : list) {
listMap.add(getSoMapByModel(model));
}
return listMap;
}
/** 克隆指定key,返回一个新的SoMap */
public SoMap cloneKeys(String... keys) {
SoMap so = new SoMap();
for (String key : keys) {
so.set(key, this.get(key));
}
return so;
}
/** 克隆所有key,返回一个新的SoMap */
public SoMap cloneSoMap() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(key, this.get(key));
}
return so;
}
/** 将所有key转为大写 */
public SoMap toUpperCase() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(key.toUpperCase(), this.get(key));
}
this.clearAll().setMap(so);
return this;
}
/** 将所有key转为小写 */
public SoMap toLowerCase() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(key.toLowerCase(), this.get(key));
}
this.clearAll().setMap(so);
return this;
}
/** 将所有key中下划线转为中划线模式 (kebab-case风格) */
public SoMap toKebabCase() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(wordEachKebabCase(key), this.get(key));
}
this.clearAll().setMap(so);
return this;
}
/** 将所有key中下划线转为小驼峰模式 */
public SoMap toHumpCase() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(wordEachBigFs(key), this.get(key));
}
this.clearAll().setMap(so);
return this;
}
/** 将所有key中小驼峰转为下划线模式 */
public SoMap humpToLineCase() {
SoMap so = new SoMap();
for (String key : this.keySet()) {
so.set(wordHumpToLine(key), this.get(key));
}
this.clearAll().setMap(so);
return this;
}
// ============================= 辅助方法 =============================
/** 指定key是否为null,判定标准为 NULL_ELEMENT_ARRAY 中的元素 */
public boolean isNull(String key) {
return valueIsNull(get(key));
}
/** 指定key列表中是否包含value为null的元素,只要有一个为null,就会返回true */
public boolean isContainNull(String ...keys) {
for (String key : keys) {
if(this.isNull(key)) {
return true;
}
}
return false;
}
/** 与isNull()相反 */
public boolean isNotNull(String key) {
return !isNull(key);
}
/** 指定key的value是否为null,作用同isNotNull() */
public boolean has(String key) {
return !isNull(key);
}
/** 指定value在此SoMap的判断标准中是否为null */
public boolean valueIsNull(Object value) {
return NULL_ELEMENT_LIST.contains(value);
}
/** 验证指定key不为空,为空则抛出异常 */
public SoMap checkNull(String ...keys) {
for (String key : keys) {
if(this.isNull(key)) {
throw new RuntimeException("参数" + key + "不能为空");
}
}
return this;
}
static Pattern patternNumber = Pattern.compile("[0-9]*");
/** 指定key是否为数字 */
public boolean isNumber(String key) {
String value = getString(key);
if(value == null) {
return false;
}
return patternNumber.matcher(value).matches();
}
/**
* 转为JSON字符串
*/
public String toJsonString() {
try {
// SoMap so = SoMap.getSoMap(this);
return new ObjectMapper().writeValueAsString(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// /**
// * 转为JSON字符串, 带格式的
// */
// public String toJsonFormatString() {
// try {
// return JSON.toJSONString(this, true);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
// ============================= web辅助 =============================
/**
* 返回当前request请求的的所有参数
* @return
*/
public static SoMap getRequestSoMap() {
// 大善人SpringMVC提供的封装
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(servletRequestAttributes == null) {
throw new RuntimeException("当前线程非JavaWeb环境");
}
// 当前request
HttpServletRequest request = servletRequestAttributes.getRequest();
if (request.getAttribute("currentSoMap") == null || request.getAttribute("currentSoMap") instanceof SoMap == false ) {
initRequestSoMap(request);
}
return (SoMap)request.getAttribute("currentSoMap");
}
/** 初始化当前request的 SoMap */
private static void initRequestSoMap(HttpServletRequest request) {
SoMap soMap = new SoMap();
Map<String, String[]> parameterMap = request.getParameterMap(); // 获取所有参数
for (String key : parameterMap.keySet()) {
try {
String[] values = parameterMap.get(key); // 获得values
if(values.length == 1) {
soMap.set(key, values[0]);
} else {
List<String> list = new ArrayList<String>();
for (String v : values) {
list.add(v);
}
soMap.set(key, list);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
request.setAttribute("currentSoMap", soMap);
}
/**
* 验证返回当前线程是否为JavaWeb环境
* @return
*/
public static boolean isJavaWeb() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装
if(servletRequestAttributes == null) {
return false;
}
return true;
}
// ============================= 常见key (以下key经常用,所以封装以下,方便写代码) =============================
/** get 当前页 */
public int getKeyPageNo() {
int pageNo = getInt("pageNo", 1);
if(pageNo <= 0) {
pageNo = 1;
}
return pageNo;
}
/** get 页大小 */
public int getKeyPageSize() {
int pageSize = getInt("pageSize", 10);
if(pageSize <= 0 || pageSize > 1000) {
pageSize = 10;
}
return pageSize;
}
/** get 排序方式 */
public int getKeySortType() {
return getInt("sortType");
}
// ============================= 工具方法 =============================
/**
* 将一个一维集合转换为树形集合
* @param list 集合
* @param idKey id标识key
* @param parentIdKey 父id标识key
* @param childListKey 子节点标识key
* @return 转换后的tree集合
*/
public static List<SoMap> listToTree(List<SoMap> list, String idKey, String parentIdKey, String childListKey) {
// 声明新的集合,存储tree形数据
List<SoMap> newTreeList = new ArrayList<SoMap>();
// 声明hash-Map,方便查找数据
SoMap hash = new SoMap();
// 将数组转为Object的形式,key为数组中的id
for (int i = 0; i < list.size(); i++) {
SoMap json = (SoMap) list.get(i);
hash.put(json.getString(idKey), json);
}
// 遍历结果集
for (int j = 0; j < list.size(); j++) {
// 单条记录
SoMap aVal = (SoMap) list.get(j);
// 在hash中取出key为单条记录中pid的值
SoMap hashVp = (SoMap) hash.get(aVal.get(parentIdKey, "").toString());
// 如果记录的pid存在,则说明它有父节点,将她添加到孩子节点的集合中
if (hashVp != null) {
// 检查是否有child属性,有则添加,没有则新建
if (hashVp.get(childListKey) != null) {
@SuppressWarnings("unchecked")
List<SoMap> ch = (List<SoMap>) hashVp.get(childListKey);
ch.add(aVal);
hashVp.put(childListKey, ch);
} else {
List<SoMap> ch = new ArrayList<SoMap>();
ch.add(aVal);
hashVp.put(childListKey, ch);
}
} else {
newTreeList.add(aVal);
}
}
return newTreeList;
}
/** 指定字符串的字符串下划线转大写模式 */
private static String wordEachBig(String str){
String newStr = "";
for (String s : str.split("_")) {
newStr += wordFirstBig(s);
}
return newStr;
}
/** 返回下划线转小驼峰形式 */
private static String wordEachBigFs(String str){
return wordFirstSmall(wordEachBig(str));
}
/** 将指定单词首字母大写 */
private static String wordFirstBig(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1, str.length());
}
/** 将指定单词首字母小写 */
private static String wordFirstSmall(String str) {
return str.substring(0, 1).toLowerCase() + str.substring(1, str.length());
}
/** 下划线转中划线 */
private static String wordEachKebabCase(String str) {
return str.replaceAll("_", "-");
}
/** 驼峰转下划线 */
private static String wordHumpToLine(String str) {
return str.replaceAll("[A-Z]", "_$0").toLowerCase();
}
}
@@ -1,18 +1,18 @@
server:
port: 8001
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-server
# OAuth2.0 配置
oauth2:
is-code: true
is-implicit: true
is-password: true
is-client: true
spring:
# 静态文件路径映射
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
# static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-server\src\main\resources\static\
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-server
# redis配置
redis:
# Redis数据库索引(默认为0
@@ -1,93 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>服务提供方-确认授权页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 300px; margin: 50px auto; padding: 100px; border: 1px #000 solid;}
</style>
</head>
<body>
<div class="login-box">
<h2>服务提供方-确认授权页</h2> <br>
<div>
<div>应用id: <span class="client_id"></span></div>
<div>请求授权: <span class="scope"></span></div>
<br>
<div>是否同意授权: </div>
<div>
<button onclick="ok()">同意</button>
<button onclick="no()">拒绝</button>
</div>
</div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 获取code详细信息
var code = getParam("code", null);
var codeObj = null;
if(code == null) {
throw alert("无效code");
}
$.ajax({
url: '/oauth2/getCodeInfo',
data: {code: code},
dataType: 'json',
success: function(res) {
if(res.data == null) {
return alert('无效授权码');
}
codeObj = res.data;
$(".client_id").html(res.data.clientId);
$(".scope").html(res.data.scope);
},
error: function(e, ee, eee) {
console.log('error');
alert(eee);
}
});
// 确认授权
function ok() {
$.ajax({
url: '/oauth2/confirm',
data: {code: code},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
// 跳转
location.href = codeObj.redirectUri;
} else {
alert(res.msg);
}
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 拒绝授权时,跳入拒绝授权地址
function no() {
// alert(codeObj.rejectUri)
location.href = codeObj.rejectUri;
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>
@@ -1,53 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>服务提供方-登录页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 50px auto;}
</style>
</head>
<body>
<div class="login-box">
<h2>服务提供方-登录页</h2> <br>
<div>注:您当前在服务提供方尚未登录,请先登录</div>
<div>测试账号: test test</div> <br>
账号:<input name="username" /> <br>
密码:<input name="password" type="password" /> <br>
<button onclick="doLogin()">登录</button>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 登录方法
function getLoginId() {
}
// 登录方法
function doLogin() {
console.log('-----------');
$.ajax({
url: '/doLogin',
data: {
username: $('[name=username]').val(),
password: $('[name=password]').val()
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
alert('登录成功');
location.reload(true);
} else {
alert('登录失败');
}
},
error: function(e) {
console.log('error');
}
});
}
</script>
</body>
</html>
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-认证中心-确认授权页</title>
<style type="text/css">
body{background-color: #F5F5D5;}
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 20vh auto; padding: 70px; border: 1px #000 solid;}
.login-box button{padding: 5px 15px; cursor: pointer; }
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-认证中心-确认授权页</h2> <br>
<div>
<div><b>应用ID</b><span th:utext="${clientId}"></span></div>
<div><b>请求授权:</b><span th:utext="${scope}"></span></div>
<br><div>------------- 是否同意授权 -------------</div><br>
<div>
<button onclick="yes()">同意</button>
<button onclick="no()">拒绝</button>
</div>
</div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 同意授权
function yes() {
console.log('-----------');
$.ajax({
url: '/oauth2/doConfirm',
data: {
client_id: getParam('client_id'),
scope: getParam('scope')
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
layer.msg('授权成功!');
setTimeout(function() {
location.reload(true);
}, 800);
} else {
// 重定向至授权失败URL
layer.alert('授权失败!');
}
},
error: function(e) {
console.log('error');
}
});
}
// 拒绝授权
function no() {
var url = joinParam(getParam('redirect_uri'), "handle=refuse&msg=用户拒绝了授权");
location.href = url;
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
// 在url上拼接上kv参数并返回
function joinParam(url, parameStr) {
if(parameStr == null || parameStr.length == 0) {
return url;
}
var index = url.indexOf('?');
// ? 不存在
if(index == -1) {
return url + '?' + parameStr;
}
// ? 是最后一位
if(index == url.length - 1) {
return url + parameStr;
}
// ? 是其中一位
if(index > -1 && index < url.length - 1) {
// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
if(url.lastIndexOf('&') != url.length - 1 && parameStrindexOf('&') != 0) {
return url + '&' + parameStr;
} else {
return url + parameStr;
}
}
}
</script>
</body>
</html>
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-OAuth2-认证中心-登录页</title>
<style type="text/css">
body{background-color: #F5F5D5;}
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 20vh auto;}
.login-box input{line-height: 25px; margin-bottom: 10px;}
.login-box button{padding: 5px 15px; cursor: pointer; }
</style>
</head>
<body>
<div class="login-box">
<h2>Sa-OAuth2-认证中心-登录页</h2> <br>
账号:<input name="name" /> <br>
密码:<input name="pwd" type="password" /> <br>
<button onclick="doLogin()">登录</button>
<span style="color: #666;">(测试账号: sa 123456</span>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 登录方法
function doLogin() {
console.log('-----------');
$.ajax({
url: '/oauth2/doLogin',
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
layer.msg('登录成功!');
setTimeout(function() {
location.reload(true);
}, 800);
} else {
layer.alert(res.msg);
}
},
error: function(e) {
console.log('error');
}
});
}
</script>
</body>
</html>
@@ -15,7 +15,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
@@ -16,6 +16,10 @@ sa:
title: Sa-Token 登录
# 是否显示底部版权信息
copr: true
# 指定拦截路径
# include: /**
# 指定排除路径
# exclude: /sss,/fff
# 将本地磁盘的某个路径作为静态资源开放
# dir: file:E:\static
+1 -1
View File
@@ -9,7 +9,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
@@ -107,9 +107,9 @@ public class TestController {
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
System.out.println("测试取值name" + StpUtil.getSession().get("name"));
StpUtil.getSession().set("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().get("name"));
System.out.println( ONode.stringify(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
@@ -120,10 +120,10 @@ public class TestController {
System.out.println("======================= 进入方法,测试自定义session接口 ========================= ");
// 自定义session就是无需登录也可以使用 的session 比如拿用户的手机号当做 key 来获取 session
System.out.println("自定义 session的id为:" + SaSessionCustomUtil.getSessionById("1895544896").getId());
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").getAttribute("name"));
SaSessionCustomUtil.getSessionById("1895544896").setAttribute("name", "张三"); // 写入值
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").getAttribute("name"));
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").getAttribute("name"));
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
SaSessionCustomUtil.getSessionById("1895544896").set("name", "张三"); // 写入值
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
System.out.println("测试取值name" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
return AjaxJson.getSuccess();
}
@@ -135,9 +135,9 @@ public class TestController {
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前token专属session: " + StpUtil.getTokenSession().getId());
System.out.println("测试取值name" + StpUtil.getTokenSession().getAttribute("name"));
StpUtil.getTokenSession().setAttribute("name", "张三"); // 写入一个值
System.out.println("测试取值name" + StpUtil.getTokenSession().getAttribute("name"));
System.out.println("测试取值name" + StpUtil.getTokenSession().get("name"));
StpUtil.getTokenSession().set("name", "张三"); // 写入一个值
System.out.println("测试取值name" + StpUtil.getTokenSession().get("name"));
return AjaxJson.getSuccess();
}
@@ -2,24 +2,24 @@
server:
port: 8081
solon:
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
solon:
# redis配置
redis:
# Redis数据库索引(默认为0
@@ -11,17 +11,18 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
<sa-token-version>1.24.0</sa-token-version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -38,32 +39,32 @@
<version>${sa-token-version}</version>
</dependency>
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- Sa-Token 整合 Redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- 提供Redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- sa-token整合SpringAOP实现注解鉴权 -->
<!-- <dependency>
<!-- Sa-Token整合SpringAOP实现注解鉴权 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>${sa-token-version}</version>
</dependency> -->
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
@@ -79,7 +80,6 @@
<version>5.5.4</version>
</dependency> -->
</dependencies>
@@ -15,7 +15,7 @@ public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig());
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
}

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