Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed7b1cef69 | |||
| 17235e0d2c | |||
| 8a72c69474 | |||
| 3bec16627e | |||
| d491f4083f | |||
| 643118177a | |||
| cf6632df79 | |||
| 474a560691 | |||
| 040777be7c | |||
| 977ab3ed40 | |||
| b409df78b3 | |||
| 22141193d4 | |||
| d7f7a89af2 | |||
| e5f751d004 | |||
| d107b6b341 | |||
| dc1e01509d | |||
| f260f6028a | |||
| 4ba55ff687 | |||
| 3e0ae863e5 | |||
| 826760f160 | |||
| dc6b0ba061 | |||
| ba5784abec | |||
| b7dba13cab | |||
| 713e11482c | |||
| 356e65f749 | |||
| e254167398 | |||
| 7b2d402acb | |||
| 0e16f76700 | |||
| 3e09e68b6e | |||
| 022e286904 | |||
| 7acf5e9790 | |||
| 4fb1ea96fb | |||
| 32bb677e26 | |||
| 9aa6e5db42 | |||
| 854774a1f1 | |||
| 73e2b2d593 | |||
| fd8d0f6d92 | |||
| a0715b8aea | |||
| 9a58702eb3 | |||
| cff04c331c | |||
| 23a85700af | |||
| 97bc0211d7 | |||
| 6ccb8ea38b | |||
| 765c4ae9f2 | |||
| 23da73f032 | |||
| 99a45be22f | |||
| d029a28488 | |||
| 5dc1aeb774 | |||
| 9bd367b877 | |||
| 3a4c1ef8f0 | |||
| 2e5b166ab9 | |||
| 37d38ba2b4 | |||
| d6e11ce5d9 | |||
| 96c279f06c | |||
| 84d52f1631 | |||
| 1603872c83 | |||
| c63c9d6e20 | |||
| 35deae30f5 | |||
| 1cea62ff22 | |||
| ad79e3595e | |||
| 2401726e01 | |||
| c129be45ed | |||
| be6cd36640 | |||
| f745dcefd2 | |||
| 949da31ae0 | |||
| f9c4aa1549 | |||
| 3eb294668e | |||
| cc4cc89e27 | |||
| 3567e9f54b | |||
| 84679d5fd4 | |||
| adbc1003b4 | |||
| 35cf52c684 | |||
| 38903d5a46 | |||
| 9530e63d3b | |||
| be51ff27a1 | |||
| bcc1e0cc93 | |||
| 5294b23652 | |||
| 142f2994cb | |||
| 4e7ab59dcf | |||
| ef4514d873 | |||
| efc41e1504 | |||
| 66639eda83 | |||
| 798a5548f9 | |||
| 1644a1c5f3 | |||
| d8ad47193e | |||
| 35814bbb66 | |||
| ef1bdbd867 | |||
| 4d10dd0d48 | |||
| d6b5615635 | |||
| 5855e71413 | |||
| 4eaddefc21 | |||
| fbe63e591e | |||
| 0db3312a63 | |||
| 2aad6d4413 | |||
| 8651ddad1e | |||
| 952fa04658 | |||
| 1f896ef584 | |||
| 03f66c6081 | |||
| 6a3b058dd3 | |||
| c1ab2cdd82 | |||
| a88c8058fd | |||
| 02f082b400 | |||
| 3372cf190e | |||
| d4493d0f98 | |||
| 5d5e2a5d52 | |||
| 64beb7a18a | |||
| bffdd0f2e1 | |||
| fef0d8f3e2 | |||
| bc8339e13d | |||
| bea2592dc9 | |||
| cf93324053 | |||
| cbb7713c9f | |||
| 010f4d3c88 | |||
| 2842a7a56e | |||
| ef1507e5b7 | |||
| 95beaee6ee | |||
| cbc28d392b | |||
| 23a9fb3447 | |||
| 93714d28e0 | |||
| b5f23f2455 |
@@ -0,0 +1,18 @@
|
||||
### 使用版本:
|
||||
请提供一下版本号
|
||||
|
||||
|
||||
### 报错信息:
|
||||
请提供报错的详细信息
|
||||
|
||||
|
||||
### 希望结果:
|
||||
相比于已发生的报错,您希望看到什么样的运行结果
|
||||
|
||||
|
||||
### 复现步骤:
|
||||
如果复现步骤比较复杂,请将 demo 上传到 git 并留下地址
|
||||
|
||||
|
||||
备注:您提供的信息越充足,我们将越能快速的定位错误
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
### 使用版本:
|
||||
请提供一下版本号
|
||||
|
||||
|
||||
### 报错信息:
|
||||
请提供报错的详细信息
|
||||
|
||||
|
||||
### 希望结果:
|
||||
相比于已发生的报错,您希望看到什么样的运行结果
|
||||
|
||||
|
||||
### 复现步骤:
|
||||
如果复现步骤比较复杂,请将 demo 上传到 git 并留下地址
|
||||
|
||||
|
||||
备注:您提供的信息越充足,我们将越能快速的定位错误
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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.23.0</h1>
|
||||
<h4 align="center">这可能是史上功能最全的 Java 权限认证框架!</h4>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.27.0</h1>
|
||||
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg"></a>
|
||||
<a href="https://gitee.com/dromara/sa-token/members"><img src="https://gitee.com/dromara/sa-token/badge/fork.svg"></a>
|
||||
@@ -15,31 +15,92 @@
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 在线资料
|
||||
|
||||
## 前言:
|
||||
- [在线文档:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
|
||||
|
||||
- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
|
||||
- 我们将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践。
|
||||
|
||||
- [开源不易,求鼓励,点个star吧 !](###)
|
||||
- 注:学习测试请拉取 master 分支,dev 为正在开发的分支,有很多特性并不稳定。
|
||||
|
||||
- 开源不易,点个 star 鼓励一下吧!
|
||||
|
||||
## Sa-Token 介绍
|
||||
Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题
|
||||
|
||||
框架集成简单、开箱即用、API设计清爽,通过Sa-Token,你将以一种极其简单的方式实现系统的权限认证部分
|
||||
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`**
|
||||
等一系列权限相关问题。
|
||||
|
||||
- **登录验证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
|
||||
- **权限验证** —— 权限认证、角色认证、会话二级认证
|
||||
Sa-Token 的 API 设计非常简单,有多简单呢?以登录认证为例,你只需要:
|
||||
|
||||
``` java
|
||||
// 在登录时写入当前会话的账号id
|
||||
StpUtil.login(10001);
|
||||
|
||||
// 然后在需要校验登录处调用以下方法:
|
||||
// 如果当前会话未登录,这句代码会抛出 `NotLoginException` 异常
|
||||
StpUtil.checkLogin();
|
||||
```
|
||||
|
||||
至此,我们已经借助 Sa-Token 完成登录认证!
|
||||
|
||||
此时的你小脑袋可能飘满了问号,就这么简单?自定义 Realm 呢?全局过滤器呢?我不用写各种配置文件吗?
|
||||
|
||||
没错,在 Sa-Token 中,登录认证就是如此简单,不需要任何的复杂前置工作,只需这一行简单的API调用,就可以完成会话登录认证!
|
||||
|
||||
当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
|
||||
|
||||
权限认证示例(只有具备 `user:add` 权限的会话才可以进入请求)
|
||||
``` java
|
||||
@SaCheckPermission("user:add")
|
||||
@RequestMapping("/user/insert")
|
||||
public String insert(SysUser user) {
|
||||
// ...
|
||||
return "用户增加";
|
||||
}
|
||||
```
|
||||
|
||||
将某个账号踢下线(待到对方再次访问系统时会抛出`NotLoginException`异常)
|
||||
``` java
|
||||
// 将账号id为 10001 的会话踢下线
|
||||
StpUtil.kickout(10001);
|
||||
```
|
||||
|
||||
在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
|
||||
``` java
|
||||
StpUtil.login(10001); // 标记当前会话登录的账号id
|
||||
StpUtil.getLoginId(); // 获取当前会话登录的账号id
|
||||
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
|
||||
StpUtil.logout(); // 当前会话注销登录
|
||||
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
|
||||
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
|
||||
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
|
||||
StpUtil.getSession(); // 获取当前账号id的Session
|
||||
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
|
||||
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
|
||||
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
|
||||
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
|
||||
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
|
||||
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
|
||||
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
|
||||
```
|
||||
|
||||
即使不运行测试,相信您也能意会到绝大多数 API 的用法。
|
||||
|
||||
|
||||
|
||||
## Sa-Token 功能一览
|
||||
|
||||
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
|
||||
- **权限认证** —— 权限认证、角色认证、会话二级认证
|
||||
- **Session会话** —— 全端共享Session、单端独享Session、自定义Session
|
||||
- **踢人下线** —— 根据账号id踢人下线、根据Token值踢人下线
|
||||
- **账号封禁** —— 指定天数封禁、永久封禁、设定解封时间
|
||||
- **持久层扩展** —— 可集成Redis、Memcached等专业缓存中间件,重启数据不丢失
|
||||
- **分布式会话** —— 提供jwt集成、共享数据中心两种分布式会话方案
|
||||
- **微服务网关鉴权** —— 适配Gateway、Soul、Zuul等常见网关的路由拦截认证
|
||||
- **微服务网关鉴权** —— 适配Gateway、ShenYu、Zuul等常见网关的路由拦截认证
|
||||
- **单点登录** —— 内置三种单点登录模式:无论是否跨域、是否共享Redis,都可以搞定
|
||||
- **OAuth2.0认证** —— 基于RFC-6749标准编写,OAuth2.0标准流程的授权认证,支持openid模式
|
||||
- **二级认证** —— 在已登录的基础上再次认证,保证安全性
|
||||
- **Basic认证** —— 一行代码接入 Http Basic 认证
|
||||
- **独立Redis** —— 将权限缓存与业务缓存分离
|
||||
- **临时Token验证** —— 解决短时间的Token授权问题
|
||||
- **模拟他人账号** —— 实时操作任意用户状态数据
|
||||
@@ -58,12 +119,6 @@ Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证
|
||||
- **开箱即用** —— 提供SpringMVC、WebFlux等常见web框架starter集成包,真正的开箱即用
|
||||
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
|
||||
|
||||
##### Sa-Token 功能结构图
|
||||

|
||||
|
||||
##### Sa-Token 认证流程图
|
||||

|
||||
|
||||
|
||||
## Sa-Token-SSO 单点登录
|
||||
对于单点登录,网上教程大多以CAS模式为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
|
||||
@@ -83,7 +138,7 @@ Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证
|
||||
|
||||
## Sa-Token-SSO 特性
|
||||
1. API简单易用,文档介绍详细,且提供直接可用的集成示例
|
||||
2. 支持三种模式,不论是否跨域、是否共享Redis,都可以完美解决
|
||||
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权限认证如何整合……
|
||||
@@ -101,59 +156,11 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
|
||||
详细参考文档:[http://sa-token.dev33.cn/doc/index.html#/oauth2/readme](http://sa-token.dev33.cn/doc/index.html#/oauth2/readme)
|
||||
|
||||
|
||||
## 代码示例
|
||||
|
||||
Sa-Token的API调用非常简单,有多简单呢?以登录验证为例,你只需要:
|
||||
## Sa-Token 功能结构图
|
||||

|
||||
|
||||
``` java
|
||||
// 在登录时写入当前会话的账号id
|
||||
StpUtil.login(10001);
|
||||
|
||||
// 然后在任意需要校验登录处调用以下API
|
||||
// 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
|
||||
StpUtil.checkLogin();
|
||||
```
|
||||
至此,我们已经借助Sa-Token框架完成登录授权!
|
||||
|
||||
此时的你小脑袋可能飘满了问号,就这么简单?自定义Realm呢?全局过滤器呢?我不用写各种配置文件吗?
|
||||
|
||||
事实上在此我可以负责的告诉你,在Sa-Token中,登录授权就是如此的简单,不需要什么全局过滤器,不需要各种乱七八糟的配置!只需要这一行简单的API调用,即可完成会话的登录授权!
|
||||
|
||||
当你受够Shiro、Spring Security等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token的API设计是多么的清爽!
|
||||
|
||||
权限认证示例 (只有具有`user:add`权限的会话才可以进入请求)
|
||||
``` java
|
||||
@SaCheckPermission("user:add")
|
||||
@RequestMapping("/user/insert")
|
||||
public String insert(SysUser user) {
|
||||
// ...
|
||||
return "用户增加";
|
||||
}
|
||||
```
|
||||
|
||||
将某个账号踢下线 (待到对方再次访问系统时会抛出`NotLoginException`异常)
|
||||
``` java
|
||||
// 使账号id为10001的会话注销登录
|
||||
StpUtil.logoutByLoginId(10001);
|
||||
```
|
||||
|
||||
除了以上的示例,Sa-Token还可以一行代码完成以下功能:
|
||||
``` java
|
||||
StpUtil.login(10001); // 标记当前会话登录的账号id
|
||||
StpUtil.getLoginId(); // 获取当前会话登录的账号id
|
||||
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
|
||||
StpUtil.logout(); // 当前会话注销登录
|
||||
StpUtil.logoutByLoginId(10001); // 让账号为10001的会话注销登录(踢人下线)
|
||||
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
|
||||
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
|
||||
StpUtil.getSession(); // 获取当前账号id的Session
|
||||
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
|
||||
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
|
||||
StpUtil.login(10001, "PC"); // 指定设备标识登录
|
||||
StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
|
||||
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
|
||||
```
|
||||
Sa-Token API 众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档
|
||||

|
||||
|
||||
|
||||
## Star 趋势
|
||||
@@ -162,41 +169,43 @@ Sa-Token API 众多,请恕此处无法为您逐一展示,更多示例请戳
|
||||
[](https://starchart.cc/dromara/sa-token)
|
||||
|
||||
|
||||
## 参与贡献
|
||||
众人拾柴火焰高,万丈高楼众人起!
|
||||
Sa-Token秉承着开放的思想,欢迎大家为框架添砖加瓦:
|
||||
## 使用Sa-Token的开源项目
|
||||
- **[ sa-plus ]**:[一个基于 SpringBoot 架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus)
|
||||
|
||||
1. 核心代码:该部分需要开发者了解整个框架的架构,遵循已有代码规范进行bug修复或提交新功能
|
||||
2. 文档部分:需要以清晰明了的语句书写文档,力求简单易读,授人以鱼同时更授人以渔
|
||||
3. 社区建设:如果框架帮助到了您,希望您可以加入qq群参与交流,对不熟悉框架的新人进行排难解惑
|
||||
4. 框架推广:一个优秀的开源项目不能仅靠闭门造车,它还需要一定的推广方案让更多的人一起参与到项目中
|
||||
5. 其它部分:您可以参考项目issues与需求墙进行贡献
|
||||
- **[ jthink ]**: [一个基于 SpringBoot + Sa-Token + Thymeleaf 的博客系统](https://gitee.com/wtsoftware/jthink)
|
||||
|
||||
作者寄语:参与贡献不光只有提交代码,点一个star、提一个issues都是对开源项目的促进,
|
||||
如果Sa-Token帮助到了你,欢迎你把框架推荐给朋友、同事使用,为Sa-Token的推广做一份贡献
|
||||
- **[ 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的开源项目
|
||||
[**[ sa-plus ]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus)
|
||||
- **[ sa-token-plugin ]**:[Sa-Token第三方插件实现,基于Sa-Token-Core,提供一些与官方不同实现机制的的插件集合,作为Sa-Token开源生态的补充](https://gitee.com/bootx/sa-token-plugin)
|
||||
|
||||
[**[ jthink ]** 一个基于springboot+sa-token+thymeleaf的博客系统](https://gitee.com/wtsoftware/jthink)
|
||||
- **[ easy-admin ]**:[一个基于SpringBoot2 + Sa-Token + Mybatis-Plus + Snakerflow + Layui 的后台管理系统,灵活多变可前后端分离,也可单体,内置代码生成器、权限管理、工作流引擎等](https://gitee.com/lakernote/easy-admin)
|
||||
|
||||
- **[ RuoYi-Vue-Plus ]**:[基于 RuoYi-Vue 集成 SaToken + Lombok + Mybatis-Plus + Undertow + knife4j + Hutool + Feign 重写所有原生业务 定期与 RuoYi-Vue 同步](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/tree/satoken/)
|
||||
|
||||
[**[ dcy-fast ]** 一个基于springboot+sa-token+mybatis-plus的后台管理系统,前端vue-element-admin,并且内置代码生成器](https://gitee.com/dcy421/dcy-fast)
|
||||
|
||||
如果您的项目使用了Sa-Token,欢迎提交pr
|
||||
|
||||
|
||||
## 友情链接
|
||||
[**[ okhttps ]** 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
|
||||
- **[ OkHttps ]**:[ 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
|
||||
|
||||
- **[ 小诺快速开发平台 ]**:[ 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
|
||||
|
||||
- **[ Jpom ]**:[ 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件](https://gitee.com/dromara/Jpom)
|
||||
|
||||
- **[ TLog ]**:[ 一个轻量级的分布式日志标记追踪神器](https://gitee.com/dromara/TLog)
|
||||
|
||||
[**[ 小诺快速开发平台 ]** 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
|
||||
|
||||
|
||||
## 交流群
|
||||
QQ交流群:1002350610 [点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
|
||||
QQ交流群:1群:1002350610 (已满) 、
|
||||
2群:614714762 [点击加入](https://jq.qq.com/?_wv=1027&k=b759RZrL)
|
||||
|
||||
微信交流群:
|
||||
|
||||
<!--  -->
|
||||
|
||||

|
||||
|
||||
(扫码添加微信,备注:sa-token,邀您加入群聊)
|
||||
|
||||
@@ -37,11 +37,15 @@ cd sa-token-demo-alone-redis
|
||||
call mvn clean
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-sso1
|
||||
cd sa-token-demo-thymeleaf
|
||||
call mvn clean
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-sso2-server
|
||||
cd sa-token-demo-sso-server
|
||||
call mvn clean
|
||||
cd ..
|
||||
|
||||
cd sa-token-demo-sso1-client
|
||||
call mvn clean
|
||||
cd ..
|
||||
|
||||
@@ -49,10 +53,6 @@ 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 ..
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.23.0</version>
|
||||
<version>1.27.0</version>
|
||||
|
||||
<!-- 项目介绍 -->
|
||||
<name>sa-token</name>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.23.0</version>
|
||||
<version>1.27.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -27,12 +27,13 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SaManager {
|
||||
|
||||
/**
|
||||
* 配置文件 Bean
|
||||
*/
|
||||
public static SaTokenConfig config;
|
||||
public volatile static SaTokenConfig config;
|
||||
public static void setConfig(SaTokenConfig config) {
|
||||
SaManager.config = config;
|
||||
if(config.getIsPrint()) {
|
||||
@@ -55,7 +56,7 @@ public class SaManager {
|
||||
/**
|
||||
* 持久化 Bean
|
||||
*/
|
||||
private static SaTokenDao saTokenDao;
|
||||
private volatile static SaTokenDao saTokenDao;
|
||||
public static void setSaTokenDao(SaTokenDao saTokenDao) {
|
||||
if((SaManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
|
||||
((SaTokenDaoDefaultImpl)SaManager.saTokenDao).endRefreshThread();
|
||||
@@ -76,7 +77,7 @@ public class SaManager {
|
||||
/**
|
||||
* 权限认证 Bean
|
||||
*/
|
||||
private static StpInterface stpInterface;
|
||||
private volatile static StpInterface stpInterface;
|
||||
public static void setStpInterface(StpInterface stpInterface) {
|
||||
SaManager.stpInterface = stpInterface;
|
||||
}
|
||||
@@ -94,7 +95,7 @@ public class SaManager {
|
||||
/**
|
||||
* 框架行为 Bean
|
||||
*/
|
||||
private static SaTokenAction saTokenAction;
|
||||
private volatile static SaTokenAction saTokenAction;
|
||||
public static void setSaTokenAction(SaTokenAction saTokenAction) {
|
||||
SaManager.saTokenAction = saTokenAction;
|
||||
}
|
||||
@@ -110,9 +111,9 @@ public class SaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 容器操作 Bean
|
||||
* 上下文 Bean
|
||||
*/
|
||||
private static SaTokenContext saTokenContext;
|
||||
private volatile static SaTokenContext saTokenContext;
|
||||
public static void setSaTokenContext(SaTokenContext saTokenContext) {
|
||||
SaManager.saTokenContext = saTokenContext;
|
||||
}
|
||||
@@ -130,7 +131,7 @@ public class SaManager {
|
||||
/**
|
||||
* 侦听器 Bean
|
||||
*/
|
||||
private static SaTokenListener saTokenListener;
|
||||
private volatile static SaTokenListener saTokenListener;
|
||||
public static void setSaTokenListener(SaTokenListener saTokenListener) {
|
||||
SaManager.saTokenListener = saTokenListener;
|
||||
}
|
||||
@@ -148,7 +149,7 @@ public class SaManager {
|
||||
/**
|
||||
* 临时令牌验证模块 Bean
|
||||
*/
|
||||
private static SaTempInterface saTemp;
|
||||
private volatile static SaTempInterface saTemp;
|
||||
public static void setSaTemp(SaTempInterface saTemp) {
|
||||
SaManager.saTemp = saTemp;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package cn.dev33.satoken.action;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
/**
|
||||
* Sa-Token 逻辑代理接口
|
||||
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
|
||||
* <p>Sa-Token 逻辑代理接口 </p>
|
||||
* <p>此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写</p>
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public interface SaTokenAction {
|
||||
|
||||
/**
|
||||
@@ -42,4 +45,10 @@ public interface SaTokenAction {
|
||||
*/
|
||||
public void checkMethodAnnotation(Method method);
|
||||
|
||||
/**
|
||||
* 从指定元素校验注解
|
||||
* @param target /
|
||||
*/
|
||||
public void validateAnnotation(AnnotatedElement target);
|
||||
|
||||
}
|
||||
|
||||
@@ -6,19 +6,24 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.annotation.SaCheckBasic;
|
||||
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.basic.SaBasicUtil;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
/**
|
||||
* Sa-Token 逻辑代理接口 [默认实现类]
|
||||
* <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
|
||||
* <p> Sa-Token 逻辑代理接口 [默认实现类] </p>
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaTokenActionDefaultImpl implements SaTokenAction {
|
||||
|
||||
/**
|
||||
@@ -108,31 +113,38 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
|
||||
* 从指定元素校验注解
|
||||
* @param target see note
|
||||
*/
|
||||
protected void validateAnnotation(AnnotatedElement target) {
|
||||
public void validateAnnotation(AnnotatedElement target) {
|
||||
|
||||
// 校验 @SaCheckLogin 注解
|
||||
if(target.isAnnotationPresent(SaCheckLogin.class)) {
|
||||
SaCheckLogin at = target.getAnnotation(SaCheckLogin.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
|
||||
if(checkLogin != null) {
|
||||
SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
|
||||
}
|
||||
|
||||
|
||||
// 校验 @SaCheckRole 注解
|
||||
if(target.isAnnotationPresent(SaCheckRole.class)) {
|
||||
SaCheckRole at = target.getAnnotation(SaCheckRole.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
|
||||
if(checkRole != null) {
|
||||
SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
|
||||
}
|
||||
|
||||
|
||||
// 校验 @SaCheckPermission 注解
|
||||
if(target.isAnnotationPresent(SaCheckPermission.class)) {
|
||||
SaCheckPermission at = target.getAnnotation(SaCheckPermission.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
|
||||
if(checkPermission != null) {
|
||||
SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckSafe 注解
|
||||
if(target.isAnnotationPresent(SaCheckSafe.class)) {
|
||||
SaCheckSafe at = target.getAnnotation(SaCheckSafe.class);
|
||||
SaManager.getStpLogic(null).checkByAnnotation(at);
|
||||
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
|
||||
if(checkSafe != null) {
|
||||
SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckBasic 注解
|
||||
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
|
||||
if(checkBasic != null) {
|
||||
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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;
|
||||
|
||||
import cn.dev33.satoken.basic.SaBasicTemplate;
|
||||
|
||||
/**
|
||||
* Http Basic 认证:只有通过 Basic 认证后才能进入该方法
|
||||
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface SaCheckBasic {
|
||||
|
||||
/**
|
||||
* 领域
|
||||
* @return see note
|
||||
*/
|
||||
String realm() default SaBasicTemplate.DEFAULT_REALM;
|
||||
|
||||
/**
|
||||
* 需要校验的账号密码,格式形如 sa:123456
|
||||
* @return see note
|
||||
*/
|
||||
String account() default "";
|
||||
|
||||
}
|
||||
@@ -33,4 +33,19 @@ public @interface SaCheckPermission {
|
||||
*/
|
||||
String type() default "";
|
||||
|
||||
/**
|
||||
* 在权限认证不通过时的次要选择,两者只要其一认证成功即可通过校验
|
||||
*
|
||||
* <p>
|
||||
* 例1:@SaCheckPermission(value="user-add", orRole="admin"),
|
||||
* 代表本次请求只要具有 user-add权限 或 admin角色 其一即可通过校验
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 例2: orRole = {"admin", "manager", "staff"},具有三个角色其一即可 <br>
|
||||
* 例3: orRole = {"admin, manager, staff"},必须三个角色同时具备
|
||||
* </p>
|
||||
*/
|
||||
String[] orRole() default {};
|
||||
|
||||
}
|
||||
|
||||
@@ -15,4 +15,10 @@ import java.lang.annotation.Target;
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface SaCheckSafe {
|
||||
|
||||
/**
|
||||
* 多账号体系下所属的账号体系标识
|
||||
* @return see note
|
||||
*/
|
||||
String type() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.dev33.satoken.basic;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.exception.NotBasicAuthException;
|
||||
import cn.dev33.satoken.secure.SaBase64Util;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 认证模块
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaBasicTemplate {
|
||||
|
||||
/**
|
||||
* 默认的 Realm 名称
|
||||
*/
|
||||
public static final String DEFAULT_REALM = "Sa-Token";
|
||||
|
||||
/**
|
||||
* 设置响应头,并抛出异常
|
||||
* @param realm 领域
|
||||
*/
|
||||
public void throwNotBasicAuthException(String realm) {
|
||||
SaHolder.getResponse().setStatus(401).setHeader("WWW-Authenticate", "Basic Realm=" + realm);
|
||||
throw new NotBasicAuthException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public String getAuthorizationValue() {
|
||||
|
||||
// 获取请求头 Authorization 参数
|
||||
String authorization = SaHolder.getRequest().getHeader("Authorization");
|
||||
|
||||
// 如果不是以 Basic 作为前缀,则视为无效
|
||||
if(authorization == null || authorization.startsWith("Basic ") == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 裁剪前缀并解码
|
||||
return SaBase64Util.decode(authorization.substring(6));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
public void check() {
|
||||
check(DEFAULT_REALM, SaManager.getConfig().getBasic());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public void check(String account) {
|
||||
check(DEFAULT_REALM, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
|
||||
* @param realm 领域
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public void check(String realm, String account) {
|
||||
if(SaFoxUtil.isEmpty(account)) {
|
||||
account = SaManager.getConfig().getBasic();
|
||||
}
|
||||
String authorization = getAuthorizationValue();
|
||||
if(SaFoxUtil.isEmpty(authorization) || authorization.equals(account) == false) {
|
||||
throwNotBasicAuthException(realm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.dev33.satoken.basic;
|
||||
|
||||
/**
|
||||
* Sa-Token Http Basic 认证 Util
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaBasicUtil {
|
||||
|
||||
/**
|
||||
* 底层 SaBasicTemplate 对象
|
||||
*/
|
||||
public static SaBasicTemplate saBasicTemplate = new SaBasicTemplate();
|
||||
|
||||
/**
|
||||
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
|
||||
* @return 值
|
||||
*/
|
||||
public static String getAuthorizationValue() {
|
||||
return saBasicTemplate.getAuthorizationValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
|
||||
*/
|
||||
public static void check() {
|
||||
saBasicTemplate.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public static void check(String account) {
|
||||
saBasicTemplate.check(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
|
||||
* @param realm 领域
|
||||
* @param account 账号(格式为 user:password)
|
||||
*/
|
||||
public static void check(String realm, String account) {
|
||||
saBasicTemplate.check(realm, account);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
/**
|
||||
* Sa-Token Cookie写入 相关配置
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaCookieConfig {
|
||||
|
||||
/**
|
||||
* 域(写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 是否只在 https 协议下有效
|
||||
*/
|
||||
private Boolean secure = false;
|
||||
|
||||
/**
|
||||
* 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
private Boolean httpOnly = false;
|
||||
|
||||
/**
|
||||
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
*/
|
||||
private String sameSite;
|
||||
|
||||
/**
|
||||
* @return 域 (写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
|
||||
*/
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param domain 域 (写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookieConfig setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path 路径
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookieConfig setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否只在 https 协议下有效
|
||||
*/
|
||||
public Boolean getSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secure 是否只在 https 协议下有效
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookieConfig setSecure(Boolean secure) {
|
||||
this.secure = secure;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
public Boolean getHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param httpOnly 是否禁止 js 操作 Cookie
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookieConfig setHttpOnly(Boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
*/
|
||||
public String getSameSite() {
|
||||
return sameSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookieConfig setSameSite(String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
return this;
|
||||
}
|
||||
|
||||
// toString
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaCookieConfig [domain=" + domain + ", path=" + path + ", secure=" + secure + ", httpOnly=" + httpOnly
|
||||
+ ", sameSite=" + sameSite + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块 配置Model
|
||||
* Sa-Token SSO 单点登录模块 配置类 Model
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@@ -18,8 +18,11 @@ public class SaSsoConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
|
||||
// ----------------- Server端相关配置
|
||||
|
||||
/**
|
||||
* Ticket有效期 (单位: 秒)
|
||||
* Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long ticketTimeout = 60 * 5;
|
||||
|
||||
@@ -27,42 +30,77 @@ public class SaSsoConfig implements Serializable {
|
||||
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
*/
|
||||
public String allowUrl = "*";
|
||||
|
||||
/**
|
||||
* 是否打开单点注销功能
|
||||
*/
|
||||
public Boolean isSlo = true;
|
||||
|
||||
/**
|
||||
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
public Boolean isHttp = false;
|
||||
|
||||
/**
|
||||
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
public String secretkey;
|
||||
|
||||
|
||||
// ----------------- Client端相关配置
|
||||
|
||||
/**
|
||||
* SSO-Server端 单点登录地址
|
||||
* 配置 Server 端单点登录授权地址
|
||||
*/
|
||||
public String authUrl;
|
||||
|
||||
/**
|
||||
* SSO-Server端 Ticket校验地址
|
||||
* 是否打开单点注销功能
|
||||
*/
|
||||
// public Boolean isSlo = true; // 同上
|
||||
|
||||
/**
|
||||
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
// public Boolean isHttp = false; // 同上
|
||||
|
||||
/**
|
||||
* 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
// public String secretkey; // 同上
|
||||
|
||||
/**
|
||||
* 配置 Server 端的 ticket 校验地址
|
||||
*/
|
||||
public String checkTicketUrl;
|
||||
|
||||
/**
|
||||
* SSO-Server端 单点注销地址
|
||||
* 配置 Server 端查询 userinfo 地址
|
||||
*/
|
||||
public String userinfoUrl;
|
||||
|
||||
/**
|
||||
* 配置 Server 端单点注销地址
|
||||
*/
|
||||
public String sloUrl;
|
||||
|
||||
/**
|
||||
* SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
* 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String ssoLogoutCall;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return Ticket有效期 (单位: 秒)
|
||||
* @return Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long getTicketTimeout() {
|
||||
return ticketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketTimeout Ticket有效期 (单位: 秒)
|
||||
* @param ticketTimeout Ticket有效期 (单位: 秒)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
|
||||
@@ -87,14 +125,46 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
* @return 是否打开单点注销功能
|
||||
*/
|
||||
public Boolean getIsSlo() {
|
||||
return isSlo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isSlo 是否打开单点注销功能
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsSlo(Boolean isSlo) {
|
||||
this.isSlo = isSlo;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
*/
|
||||
public Boolean getIsHttp() {
|
||||
return isHttp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsHttp(Boolean isHttp) {
|
||||
this.isHttp = isHttp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
*/
|
||||
public String getSecretkey() {
|
||||
return secretkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secretkey 调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
* @param secretkey 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSecretkey(String secretkey) {
|
||||
@@ -103,14 +173,14 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO-Server端 单点登录地址
|
||||
* @return 配置的 Server 端单点登录授权地址
|
||||
*/
|
||||
public String getAuthUrl() {
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param authUrl SSO-Server端 单点登录地址
|
||||
* @param authUrl 配置 Server 端单点登录授权地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAuthUrl(String authUrl) {
|
||||
@@ -119,14 +189,15 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO-Server端Ticket校验地址
|
||||
* @return 配置的 Server 端的 ticket 校验地址
|
||||
*/
|
||||
public String getCheckTicketUrl() {
|
||||
return checkTicketUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkTicketUrl SSO-Server端Ticket校验地址
|
||||
* @param checkTicketUrl 配置 Server 端的 ticket 校验地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
|
||||
this.checkTicketUrl = checkTicketUrl;
|
||||
@@ -134,14 +205,30 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO-Server端单点注销地址
|
||||
* @return 配置的 Server 端查询 userinfo 地址
|
||||
*/
|
||||
public String getUserinfoUrl() {
|
||||
return userinfoUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userinfoUrl 配置 Server 端查询 userinfo 地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setUserinfoUrl(String userinfoUrl) {
|
||||
this.userinfoUrl = userinfoUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置 Server 端单点注销地址
|
||||
*/
|
||||
public String getSloUrl() {
|
||||
return sloUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sloUrl SSO-Server端单点注销地址
|
||||
* @param sloUrl 配置 Server 端单点注销地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSloUrl(String sloUrl) {
|
||||
@@ -150,14 +237,14 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String getSsoLogoutCall() {
|
||||
return ssoLogoutCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ssoLogoutCall SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
* @param ssoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
|
||||
@@ -167,12 +254,12 @@ public class SaSsoConfig implements Serializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", secretkey=" + secretkey
|
||||
+ ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", sloUrl=" + sloUrl
|
||||
+ ", ssoLogoutCall=" + ssoLogoutCall + ", isHttp=" + isHttp + ", isSlo=" + isSlo + "]";
|
||||
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo
|
||||
+ ", isHttp=" + isHttp + ", secretkey=" + secretkey + ", authUrl=" + authUrl + ", checkTicketUrl="
|
||||
+ checkTicketUrl + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", ssoLogoutCall="
|
||||
+ ssoLogoutCall + "]";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 以数组形式写入允许的授权回调地址
|
||||
* @param url 所有集合
|
||||
@@ -183,56 +270,11 @@ public class SaSsoConfig implements Serializable {
|
||||
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
|
||||
*/
|
||||
@@ -244,12 +286,9 @@ public class SaSsoConfig implements Serializable {
|
||||
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
|
||||
|
||||
/**
|
||||
* SSO-Client端:Ticket无效时返回的View
|
||||
* SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
|
||||
*/
|
||||
public Function<String, Object> ticketInvalidView = (ticket) -> {
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
};
|
||||
public BiFunction<Object, String, Object> ticketResultHandle = null;
|
||||
|
||||
/**
|
||||
* SSO-Client端:发送Http请求的处理函数
|
||||
@@ -276,11 +315,11 @@ public class SaSsoConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketInvalidView SSO-Client端:Ticket无效时返回的View
|
||||
* @param ticketResultHandle SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketInvalidView(Function<String, Object> ticketInvalidView) {
|
||||
this.ticketInvalidView = ticketInvalidView;
|
||||
public SaSsoConfig setTicketResultHandle(BiFunction<Object, String, Object> ticketResultHandle) {
|
||||
this.ticketResultHandle = ticketResultHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -293,4 +332,7 @@ public class SaSsoConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class SaTokenConfig implements Serializable {
|
||||
private String tokenName = "satoken";
|
||||
|
||||
/** token的长久有效期(单位:秒) 默认30天, -1代表永久 */
|
||||
private long timeout = 30 * 24 * 60 * 60;
|
||||
private long timeout = 60 * 60 * 24 * 30;
|
||||
|
||||
/**
|
||||
* token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
|
||||
@@ -53,9 +53,6 @@ public class SaTokenConfig implements Serializable {
|
||||
/** 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作) */
|
||||
private Boolean autoRenew = true;
|
||||
|
||||
/** 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景 */
|
||||
private String cookieDomain;
|
||||
|
||||
/** token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx) */
|
||||
private String tokenPrefix;
|
||||
|
||||
@@ -70,6 +67,25 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
private String jwtSecretKey;
|
||||
|
||||
/**
|
||||
* Id-Token的有效期 (单位: 秒)
|
||||
*/
|
||||
private long idTokenTimeout = 60 * 60 * 24;
|
||||
|
||||
/**
|
||||
* Http Basic 认证的账号和密码
|
||||
*/
|
||||
private String basic = "";
|
||||
|
||||
/** 配置当前项目的网络访问地址 */
|
||||
private String currDomain;
|
||||
|
||||
|
||||
/**
|
||||
* Cookie配置对象
|
||||
*/
|
||||
public SaCookieConfig cookie = new SaCookieConfig();
|
||||
|
||||
/**
|
||||
* SSO单点登录配置对象
|
||||
*/
|
||||
@@ -272,22 +288,6 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
|
||||
*/
|
||||
public String getCookieDomain() {
|
||||
return cookieDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cookieDomain 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setCookieDomain(String cookieDomain) {
|
||||
this.cookieDomain = cookieDomain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx)
|
||||
*/
|
||||
@@ -352,6 +352,54 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Id-Token的有效期 (单位: 秒)
|
||||
*/
|
||||
public long getIdTokenTimeout() {
|
||||
return idTokenTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param idTokenTimeout Id-Token的有效期 (单位: 秒)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setIdTokenTimeout(long idTokenTimeout) {
|
||||
this.idTokenTimeout = idTokenTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Http Basic 认证的账号和密码
|
||||
*/
|
||||
public String getBasic() {
|
||||
return basic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param basic Http Basic 认证的账号和密码
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setBasic(String basic) {
|
||||
this.basic = basic;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 配置当前项目的网络访问地址
|
||||
*/
|
||||
public String getCurrDomain() {
|
||||
return currDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currDomain 配置当前项目的网络访问地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setCurrDomain(String currDomain) {
|
||||
this.currDomain = currDomain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO单点登录配置对象
|
||||
*/
|
||||
@@ -359,32 +407,59 @@ public class SaTokenConfig implements Serializable {
|
||||
return sso;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param sso SSO单点登录配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
public void setSso(SaSsoConfig sso) {
|
||||
public SaTokenConfig setSso(SaSsoConfig sso) {
|
||||
this.sso = sso;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Cookie 全局配置对象
|
||||
*/
|
||||
public SaCookieConfig getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* toString()
|
||||
* @param cookie Cookie 全局配置对象
|
||||
* @return 对象自身
|
||||
*/
|
||||
@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 + ", sso=" + sso + "]";
|
||||
public SaTokenConfig setCookie(SaCookieConfig cookie) {
|
||||
this.cookie = cookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
@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
|
||||
+ ", tokenPrefix=" + tokenPrefix
|
||||
+ ", isPrint=" + isPrint
|
||||
+ ", isLog=" + isLog
|
||||
+ ", jwtSecretKey=" + jwtSecretKey
|
||||
+ ", idTokenTimeout=" + idTokenTimeout
|
||||
+ ", basic=" + basic
|
||||
+ ", currDomain=" + currDomain
|
||||
+ ", sso=" + sso
|
||||
+ ", cookie=" + cookie
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
|
||||
* @param allowConcurrentLogin see note
|
||||
@@ -401,10 +476,31 @@ public class SaTokenConfig implements Serializable {
|
||||
* @param isV see note
|
||||
* @return see note
|
||||
*/
|
||||
@Deprecated
|
||||
public SaTokenConfig setIsV(Boolean isV) {
|
||||
this.isPrint = isV;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 getCookie().getDomain() ,使用方式保持不变 </h1>
|
||||
* @return 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
|
||||
*/
|
||||
@Deprecated
|
||||
public String getCookieDomain() {
|
||||
return getCookie().getDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 getCookie().setDomain() ,使用方式保持不变 </h1>
|
||||
* @param cookieDomain 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景
|
||||
* @return 对象自身
|
||||
*/
|
||||
@Deprecated
|
||||
public SaTokenConfig setCookieDomain(String cookieDomain) {
|
||||
this.getCookie().setDomain(cookieDomain);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Properties;
|
||||
/**
|
||||
* Sa-Token配置文件的构建工厂类
|
||||
* <p>
|
||||
* 只有在非IOC环境下才会用到此类
|
||||
* 用于手动读取配置文件初始化 SaTokenConfig 对象,只有在非IOC环境下你才会用到此类
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -6,7 +6,13 @@ import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [默认实现类]
|
||||
* Sa-Token 上下文处理器 [默认实现类]
|
||||
*
|
||||
* <p>
|
||||
* 一般情况下框架会为你自动注入合适的上下文处理器,如果代码断点走到了此默认实现类,
|
||||
* 说明你引入的依赖有问题或者错误的调用了Sa-Token的API, 请在[在线开发文档 > 附录 > 常见问题排查] 中按照提示进行排查
|
||||
* </p>
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,12 @@ import cn.dev33.satoken.context.model.SaStorage;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ThreadLocal版本]
|
||||
*
|
||||
* <p>
|
||||
* 使用 [ThreadLocal版本] 上下文处理器需要在全局过滤器或者拦截器内率先调用
|
||||
* SaTokenContextForThreadLocalStorage.setBox(req,res, sto) 初始化上下文
|
||||
* </p>
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ThreadLocal版本] ---- 对象存储器
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
package cn.dev33.satoken.context.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Cookie Model
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaCookie {
|
||||
|
||||
/**
|
||||
* 写入响应头时使用的key
|
||||
*/
|
||||
public static final String HEADER_NAME = "Set-Cookie";
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
*/
|
||||
private int maxAge = -1;
|
||||
|
||||
/**
|
||||
* 域
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 是否只在 https 协议下有效
|
||||
*/
|
||||
private Boolean secure = false;
|
||||
|
||||
/**
|
||||
* 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
private Boolean httpOnly = false;
|
||||
|
||||
/**
|
||||
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
*/
|
||||
private String sameSite;
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个
|
||||
*/
|
||||
public SaCookie() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个
|
||||
* @param name 名字
|
||||
* @param value 值
|
||||
*/
|
||||
public SaCookie(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return 名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name 名称
|
||||
*/
|
||||
public SaCookie setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 值
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setValue(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
*/
|
||||
public int getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setMaxAge(int maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 域
|
||||
*/
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param domain 域
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path 路径
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否只在 https 协议下有效
|
||||
*/
|
||||
public Boolean getSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secure 是否只在 https 协议下有效
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setSecure(Boolean secure) {
|
||||
this.secure = secure;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
public Boolean getHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param httpOnly 是否禁止 js 操作 Cookie
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setHttpOnly(Boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
*/
|
||||
public String getSameSite() {
|
||||
return sameSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setSameSite(String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// toString
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
|
||||
+ ", secure=" + secure + ", httpOnly=" + httpOnly + ", sameSite="
|
||||
+ sameSite + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一下
|
||||
*/
|
||||
public void builde() {
|
||||
if(path == null) {
|
||||
path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为响应头 Set-Cookie 参数需要的值
|
||||
* @return /
|
||||
*/
|
||||
public String toHeaderValue() {
|
||||
this.builde();
|
||||
|
||||
if(SaFoxUtil.isEmpty(name)) {
|
||||
throw new SaTokenException("name不能为空");
|
||||
}
|
||||
if(value != null && value.indexOf(";") > -1) {
|
||||
throw new SaTokenException("无效Value:" + value);
|
||||
}
|
||||
|
||||
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(name + "=" + value);
|
||||
|
||||
if(maxAge >= 0) {
|
||||
sb.append("; Max-Age=" + maxAge);
|
||||
String expires;
|
||||
if(maxAge == 0) {
|
||||
expires = Instant.EPOCH.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||
} else {
|
||||
expires = OffsetDateTime.now().plusSeconds(maxAge).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||
}
|
||||
sb.append("; Expires=" + expires);
|
||||
}
|
||||
if(SaFoxUtil.isEmpty(domain) == false) {
|
||||
sb.append("; Domain=" + domain);
|
||||
}
|
||||
if(SaFoxUtil.isEmpty(path) == false) {
|
||||
sb.append("; Path=" + path);
|
||||
}
|
||||
if(secure) {
|
||||
sb.append("; Secure");
|
||||
}
|
||||
if(httpOnly) {
|
||||
sb.append("; HttpOnly");
|
||||
}
|
||||
if(SaFoxUtil.isEmpty(sameSite) == false) {
|
||||
sb.append("; sameSite=" + sameSite);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,7 +45,16 @@ public interface SaRequest {
|
||||
*/
|
||||
public default boolean isParam(String name, String value) {
|
||||
String paramValue = getParam(name);
|
||||
return paramValue != null && paramValue.equals(value);
|
||||
return SaFoxUtil.isNotEmpty(paramValue) && paramValue.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测请求是否提供了指定参数
|
||||
* @param name 参数名称
|
||||
* @return 是否提供
|
||||
*/
|
||||
public default boolean hasParam(String name) {
|
||||
return SaFoxUtil.isNotEmpty(getParam(name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +78,20 @@ public interface SaRequest {
|
||||
*/
|
||||
public String getHeader(String name);
|
||||
|
||||
/**
|
||||
* 在 [请求头] 里获取一个值
|
||||
* @param name 键
|
||||
* @param defaultValue 值为空时的默认值
|
||||
* @return 值
|
||||
*/
|
||||
public default String getHeader(String name, String defaultValue) {
|
||||
String value = getHeader(name);
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Cookie作用域] 里获取一个值
|
||||
* @param name 键
|
||||
@@ -92,7 +115,7 @@ public interface SaRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求的url,例:http://xxx.com/?id=127
|
||||
* 返回当前请求的url,不带query参数,例:http://xxx.com/test
|
||||
* @return see note
|
||||
*/
|
||||
public String getUrl();
|
||||
@@ -110,5 +133,14 @@ public interface SaRequest {
|
||||
public default boolean isAjax() {
|
||||
return getHeader("X-Requested-With") != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发请求
|
||||
* @param path 转发地址
|
||||
* @return 任意值
|
||||
*/
|
||||
public default Object forward(String path) {
|
||||
throw new SaTokenException("No implementation");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,17 +17,36 @@ public interface SaResponse {
|
||||
* 删除指定Cookie
|
||||
* @param name Cookie名称
|
||||
*/
|
||||
public void deleteCookie(String name);
|
||||
|
||||
public default void deleteCookie(String name) {
|
||||
addCookie(name, null, null, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入指定Cookie
|
||||
* 写入指定Cookie
|
||||
* @param name Cookie名称
|
||||
* @param value Cookie值
|
||||
* @param path Cookie路径
|
||||
* @param domain Cookie的作用域
|
||||
* @param timeout 过期时间 (秒)
|
||||
*/
|
||||
public void addCookie(String name, String value, String path, String domain, int timeout);
|
||||
public default void addCookie(String name, String value, String path, String domain, int timeout) {
|
||||
this.addCookie(new SaCookie(name, value).setPath(path).setDomain(domain).setMaxAge(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入指定Cookie
|
||||
* @param cookie Cookie-Model
|
||||
*/
|
||||
public default void addCookie(SaCookie cookie) {
|
||||
this.addHeader(SaCookie.HEADER_NAME, cookie.toHeaderValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应状态码
|
||||
* @param sc 响应状态码
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaResponse setStatus(int sc);
|
||||
|
||||
/**
|
||||
* 在响应头里写入一个值
|
||||
@@ -36,6 +55,14 @@ public interface SaResponse {
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaResponse setHeader(String name, String value);
|
||||
|
||||
/**
|
||||
* 在响应头里添加一个值
|
||||
* @param name 名字
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaResponse addHeader(String name, String value);
|
||||
|
||||
/**
|
||||
* 在响应头写入 [Server] 服务器名称
|
||||
|
||||
@@ -176,7 +176,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
|
||||
/**
|
||||
* 是否继续执行数据清理的线程标记
|
||||
*/
|
||||
public boolean refreshFlag;
|
||||
public volatile boolean refreshFlag;
|
||||
|
||||
|
||||
/**
|
||||
@@ -224,7 +224,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
|
||||
}
|
||||
}
|
||||
});
|
||||
refreshThread.start();
|
||||
this.refreshThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表提供的 Id-Token 无效
|
||||
*
|
||||
* @author kong
|
||||
*/
|
||||
public class IdTokenInvalidException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130144L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表提供的 Id-Token 无效
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public IdTokenInvalidException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未能通过 Http Basic 认证
|
||||
* @author kong
|
||||
*/
|
||||
public class NotBasicAuthException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130144L;
|
||||
|
||||
/** 异常提示语 */
|
||||
public static final String BE_MESSAGE = "no basic auth";
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未通过 Http Basic 认证
|
||||
*/
|
||||
public NotBasicAuthException() {
|
||||
super(BE_MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token框架内部逻辑发生错误抛出的异常
|
||||
* (自定义此异常方便开发者在做全局异常处理时分辨异常类型)
|
||||
@@ -42,4 +44,26 @@ public class SaTokenException extends RuntimeException {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果flag==true,则抛出message异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void throwBy(boolean flag, String message) {
|
||||
if(flag) {
|
||||
throw new SaTokenException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果value==null或者isEmpty,则抛出message异常
|
||||
* @param value 值
|
||||
* @param message 异常信息
|
||||
*/
|
||||
public static void throwByNull(Object value, String message) {
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
throw new SaTokenException(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表停止路由匹配
|
||||
* 一个异常:代表停止路由匹配,进入Controller
|
||||
*
|
||||
* @author kong
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.dev33.satoken.fun;
|
||||
|
||||
/**
|
||||
* 设定一个函数,并传入一个参数,方便在Lambda表达式下的函数式编程
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaParamFunction<T> {
|
||||
|
||||
/**
|
||||
* 执行的方法
|
||||
* @param r 传入的参数
|
||||
*/
|
||||
public void run(T r);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.dev33.satoken.fun;
|
||||
|
||||
/**
|
||||
* 设定一个函数,传入一个参数,并返回一个值,方便在Lambda表达式下的函数式编程
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SaParamRetFunction<T, R> {
|
||||
|
||||
/**
|
||||
* 执行的方法
|
||||
* @param param 传入的参数
|
||||
* @return 返回值
|
||||
*/
|
||||
public R run(T param);
|
||||
|
||||
}
|
||||
@@ -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 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(64);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 拼接key
|
||||
|
||||
/**
|
||||
* 拼接key:Id-Token 的存储 key
|
||||
* @return key
|
||||
*/
|
||||
public String splicingTokenSaveKey() {
|
||||
return SaManager.getConfig().getTokenName() + ":var:id-token";
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key:次级 Id-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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,22 +27,20 @@ public interface SaTokenListener {
|
||||
public void doLogout(String loginType, Object loginId, String tokenValue);
|
||||
|
||||
/**
|
||||
* 每次被踢下线时触发
|
||||
* @param loginType 账号类别
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue token值
|
||||
* @param device 设备标识
|
||||
* 每次被踢下线时触发
|
||||
* @param loginType 账号类别
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public void doLogoutByLoginId(String loginType, Object loginId, String tokenValue, String device);
|
||||
public void doKickout(String loginType, Object loginId, String tokenValue);
|
||||
|
||||
/**
|
||||
* 每次被顶下线时触发
|
||||
* @param loginType 账号类别
|
||||
* @param loginId 账号id
|
||||
* @param tokenValue token值
|
||||
* @param device 设备标识
|
||||
*/
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue, String device);
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue);
|
||||
|
||||
/**
|
||||
* 每次被封禁时触发
|
||||
|
||||
@@ -26,23 +26,23 @@ public class SaTokenListenerDefaultImpl implements SaTokenListener {
|
||||
*/
|
||||
@Override
|
||||
public void doLogout(String loginType, Object loginId, String tokenValue) {
|
||||
println("账号[" + loginId + "]注销成功");
|
||||
println("账号[" + loginId + "]注销成功 (Token=" + tokenValue + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被踢下线时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogoutByLoginId(String loginType, Object loginId, String tokenValue, String device) {
|
||||
println("账号[" + loginId + "]被踢下线 (终端: " + device + ")");
|
||||
public void doKickout(String loginType, Object loginId, String tokenValue) {
|
||||
println("账号[" + loginId + "]被踢下线 (Token=" + tokenValue + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次被顶下线时触发
|
||||
*/
|
||||
@Override
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue, String device) {
|
||||
println("账号[" + loginId + "]被顶下线 (终端: " + device + ")");
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue) {
|
||||
println("账号[" + loginId + "]被顶下线 (Token=" + tokenValue + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.dev33.satoken.router;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Http 请求各种请求类型的枚举表示
|
||||
*
|
||||
* <p> 参考:Spring - HttpMethod
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public enum SaHttpMethod {
|
||||
|
||||
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT,
|
||||
|
||||
/**
|
||||
* 代表全部请求方式
|
||||
*/
|
||||
ALL;
|
||||
|
||||
private static final Map<String, SaHttpMethod> map = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (SaHttpMethod reqMethod : values()) {
|
||||
map.put(reqMethod.name(), reqMethod);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String 转 enum
|
||||
* @param method 请求类型
|
||||
* @return ReqMethod 对象
|
||||
*/
|
||||
public static SaHttpMethod toEnum(String method) {
|
||||
if(method == null) {
|
||||
throw new SaTokenException("无效Method:" + method);
|
||||
}
|
||||
SaHttpMethod reqMethod = map.get(method.toUpperCase());
|
||||
if(reqMethod == null) {
|
||||
throw new SaTokenException("无效Method:" + method);
|
||||
}
|
||||
return reqMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* String[] 转 enum[]
|
||||
* @param methods 请求类型数组
|
||||
* @return ReqMethod 对象
|
||||
*/
|
||||
public static SaHttpMethod[] toEnumArray(String... methods) {
|
||||
SaHttpMethod [] arr = new SaHttpMethod[methods.length];
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
arr[i] = SaHttpMethod.toEnum(methods[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.dev33.satoken.router;
|
||||
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
import cn.dev33.satoken.fun.SaParamFunction;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
|
||||
/**
|
||||
* 路由匹配操作工具类
|
||||
@@ -36,6 +36,9 @@ public class SaRouter {
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatch(List<String> patterns, String path) {
|
||||
if(patterns == null) {
|
||||
return false;
|
||||
}
|
||||
for (String pattern : patterns) {
|
||||
if(isMatch(pattern, path)) {
|
||||
return true;
|
||||
@@ -44,6 +47,44 @@ public class SaRouter {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配
|
||||
* @param patterns 路由匹配符数组
|
||||
* @param path 被匹配的路由
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatch(String[] patterns, String path) {
|
||||
if(patterns == null) {
|
||||
return false;
|
||||
}
|
||||
for (String pattern : patterns) {
|
||||
if(isMatch(pattern, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配
|
||||
* @param methods Http请求方法断言数组
|
||||
* @param methodString Http请求方法
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatch(SaHttpMethod[] methods, String methodString) {
|
||||
if(methods == null) {
|
||||
return false;
|
||||
}
|
||||
for (SaHttpMethod method : methods) {
|
||||
if(method == SaHttpMethod.ALL || (method != null && method.toString().equalsIgnoreCase(methodString))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------ 使用当前URI匹配
|
||||
|
||||
/**
|
||||
* 路由匹配 (使用当前URI)
|
||||
* @param pattern 路由匹配符
|
||||
@@ -61,40 +102,229 @@ public class SaRouter {
|
||||
public static boolean isMatchCurrURI(List<String> patterns) {
|
||||
return isMatch(patterns, SaHolder.getRequest().getRequestPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配 (使用当前URI)
|
||||
* @param patterns 路由匹配符数组
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatchCurrURI(String[] patterns) {
|
||||
return isMatch(patterns, SaHolder.getRequest().getRequestPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配 (使用当前请求方式)
|
||||
* @param methods Http请求方法断言数组
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatchCurrMethod(SaHttpMethod[] methods) {
|
||||
return isMatch(methods, SaHolder.getRequest().getMethod());
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 执行相关 --------------------
|
||||
// -------------------- 开始匹配 --------------------
|
||||
|
||||
/**
|
||||
* 初始化一个SaRouterStaff,开始匹配
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff newMatch() {
|
||||
return new SaRouterStaff();
|
||||
}
|
||||
|
||||
// ----------------- path匹配
|
||||
|
||||
/**
|
||||
* 路由匹配
|
||||
* @param patterns 路由匹配符集合
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff match(String... patterns) {
|
||||
return new SaRouterStaff().match(patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配排除
|
||||
* @param patterns 路由匹配符排除数组
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff notMatch(String... patterns) {
|
||||
return new SaRouterStaff().notMatch(patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配
|
||||
* @param patterns 路由匹配符集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public static SaRouterStaff match(List<String> patterns) {
|
||||
return new SaRouterStaff().match(patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配排除
|
||||
* @param patterns 路由匹配符排除集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public static SaRouterStaff notMatch(List<String> patterns) {
|
||||
return new SaRouterStaff().notMatch(patterns);
|
||||
}
|
||||
|
||||
// ----------------- Method匹配
|
||||
|
||||
/**
|
||||
* Http请求方式匹配 (Enum)
|
||||
* @param methods Http请求方法断言数组
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff match(SaHttpMethod... methods) {
|
||||
return new SaRouterStaff().match(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配排除 (Enum)
|
||||
* @param methods Http请求方法断言排除数组
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff notMatch(SaHttpMethod... methods) {
|
||||
return new SaRouterStaff().notMatch(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配 (String)
|
||||
* @param methods Http请求方法断言数组
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff matchMethod(String... methods) {
|
||||
return new SaRouterStaff().matchMethod(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配排除 (String)
|
||||
* @param methods Http请求方法断言排除数组
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff notMatchMethod(String... methods) {
|
||||
return new SaRouterStaff().notMatchMethod(methods);
|
||||
}
|
||||
|
||||
// ----------------- 条件匹配
|
||||
|
||||
/**
|
||||
* 根据 boolean 值进行匹配
|
||||
* @param flag boolean值
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff match(boolean flag) {
|
||||
return new SaRouterStaff().match(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 boolean 值进行匹配排除
|
||||
* @param flag boolean值
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff notMatch(boolean flag) {
|
||||
return new SaRouterStaff().notMatch(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据自定义方法进行匹配 (lazy)
|
||||
* @param fun 自定义方法
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff match(SaParamRetFunction<Object, Boolean> fun) {
|
||||
return new SaRouterStaff().match(fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据自定义方法进行匹配排除 (lazy)
|
||||
* @param fun 自定义排除方法
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff notMatch(SaParamRetFunction<Object, Boolean> fun) {
|
||||
return new SaRouterStaff().notMatch(fun);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 直接指定check函数 --------------------
|
||||
|
||||
/**
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param function 要执行的方法
|
||||
* @param fun 要执行的校验方法
|
||||
*/
|
||||
public static void match(String pattern, SaFunction function) {
|
||||
if(isMatchCurrURI(pattern)) {
|
||||
function.run();
|
||||
}
|
||||
public static SaRouterStaff match(String pattern, SaFunction fun) {
|
||||
return new SaRouterStaff().match(pattern, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param fun 要执行的校验方法
|
||||
*/
|
||||
public static SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
|
||||
return new SaRouterStaff().match(pattern, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param excludePattern 要排除的路由匹配符
|
||||
* @param function 要执行的方法
|
||||
* @param fun 要执行的方法
|
||||
*/
|
||||
public static void match(String pattern, String excludePattern, SaFunction function) {
|
||||
if(isMatchCurrURI(pattern)) {
|
||||
if(isMatchCurrURI(excludePattern) == false) {
|
||||
function.run();
|
||||
}
|
||||
}
|
||||
public static SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
|
||||
return new SaRouterStaff().match(pattern, excludePattern, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param excludePattern 要排除的路由匹配符
|
||||
* @param fun 要执行的方法
|
||||
*/
|
||||
public static SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
|
||||
return new SaRouterStaff().match(pattern, excludePattern, fun);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 提前退出 --------------------
|
||||
|
||||
/**
|
||||
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff stop() {
|
||||
throw new StopMatchException();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止匹配,结束执行,向前端返回结果
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff back() {
|
||||
throw new BackResultException("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止匹配,结束执行,向前端返回结果
|
||||
* @param result 要输出的结果
|
||||
* @return SaRouterStaff
|
||||
*/
|
||||
public static SaRouterStaff back(Object result) {
|
||||
throw new BackResultException(result);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 历史API兼容 --------------------
|
||||
|
||||
/**
|
||||
* <h1>本函数设计已过时,请更换为:SaRouter.match(path...).ckeck(fun) </h1>
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param patterns 路由匹配符集合
|
||||
* @param function 要执行的方法
|
||||
*/
|
||||
@Deprecated
|
||||
public static void match(List<String> patterns, SaFunction function) {
|
||||
if(isMatchCurrURI(patterns)) {
|
||||
function.run();
|
||||
@@ -102,11 +332,13 @@ public class SaRouter {
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1>本函数设计已过时,请更换为:SaRouter.match(path...).notMatch(path...).ckeck(fun) </h1>
|
||||
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
|
||||
* @param patterns 路由匹配符集合
|
||||
* @param excludePatterns 要排除的路由匹配符集合
|
||||
* @param function 要执行的方法
|
||||
*/
|
||||
@Deprecated
|
||||
public static void match(List<String> patterns, List<String> excludePatterns, SaFunction function) {
|
||||
if(isMatchCurrURI(patterns)) {
|
||||
if(isMatchCurrURI(excludePatterns) == false) {
|
||||
@@ -114,41 +346,5 @@ public class SaRouter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param patterns 路由匹配符集合
|
||||
* @return 匹配结果包装对象
|
||||
*/
|
||||
public static IsRunFunction match(String... patterns) {
|
||||
boolean matchResult = isMatch(Arrays.asList(patterns), SaHolder.getRequest().getRequestPath());
|
||||
return new IsRunFunction(matchResult);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 其它操作 --------------------
|
||||
|
||||
/**
|
||||
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
|
||||
*/
|
||||
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("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
package cn.dev33.satoken.router;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.fun.SaParamFunction;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
|
||||
/**
|
||||
* 路由匹配操作对象
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaRouterStaff {
|
||||
|
||||
/**
|
||||
* 是否命中的标记变量
|
||||
*/
|
||||
public boolean isHit = true;
|
||||
|
||||
/**
|
||||
* @return 是否命中
|
||||
*/
|
||||
public boolean isHit() {
|
||||
return isHit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isHit 命中标记
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff setHit(boolean isHit) {
|
||||
this.isHit = isHit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置命中标记为 true
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff reset() {
|
||||
this.isHit = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ----------------- path匹配
|
||||
|
||||
/**
|
||||
* 路由匹配
|
||||
* @param patterns 路由匹配符数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff match(String... patterns) {
|
||||
if(isHit) {
|
||||
isHit = SaRouter.isMatchCurrURI(patterns);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配排除
|
||||
* @param patterns 路由匹配符排除数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatch(String... patterns) {
|
||||
if(isHit) {
|
||||
isHit = !SaRouter.isMatchCurrURI(patterns);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配
|
||||
* @param patterns 路由匹配符集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff match(List<String> patterns) {
|
||||
if(isHit) {
|
||||
isHit = SaRouter.isMatchCurrURI(patterns);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配排除
|
||||
* @param patterns 路由匹配符排除集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatch(List<String> patterns) {
|
||||
if(isHit) {
|
||||
isHit = !SaRouter.isMatchCurrURI(patterns);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// ----------------- Method匹配
|
||||
|
||||
/**
|
||||
* Http请求方法匹配 (Enum)
|
||||
* @param methods Http请求方法断言数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff match(SaHttpMethod... methods) {
|
||||
if(isHit) {
|
||||
isHit = SaRouter.isMatchCurrMethod(methods);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配排除 (Enum)
|
||||
* @param methods Http请求方法断言排除数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatch(SaHttpMethod... methods) {
|
||||
if(isHit) {
|
||||
isHit = !SaRouter.isMatchCurrMethod(methods);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配 (String)
|
||||
* @param methods Http请求方法断言数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff matchMethod(String... methods) {
|
||||
if(isHit) {
|
||||
SaHttpMethod [] arr = SaHttpMethod.toEnumArray(methods);
|
||||
isHit = SaRouter.isMatchCurrMethod(arr);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求方法匹配排除 (String)
|
||||
* @param methods Http请求方法断言排除数组
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatchMethod(String... methods) {
|
||||
if(isHit) {
|
||||
SaHttpMethod [] arr = SaHttpMethod.toEnumArray(methods);
|
||||
isHit = !SaRouter.isMatchCurrMethod(arr);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ----------------- 条件匹配
|
||||
|
||||
/**
|
||||
* 根据 boolean 值进行匹配
|
||||
* @param flag boolean值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff match(boolean flag) {
|
||||
if(isHit) {
|
||||
isHit = flag;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 boolean 值进行匹配排除
|
||||
* @param flag boolean值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatch(boolean flag) {
|
||||
if(isHit) {
|
||||
isHit = !flag;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据自定义方法进行匹配 (lazy)
|
||||
* @param fun 自定义方法
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff match(SaParamRetFunction<Object, Boolean> fun) {
|
||||
if(isHit) {
|
||||
isHit = fun.run(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据自定义方法进行匹配排除 (lazy)
|
||||
* @param fun 自定义排除方法
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff notMatch(SaParamRetFunction<Object, Boolean> fun) {
|
||||
if(isHit) {
|
||||
isHit = !fun.run(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ----------------- 函数校验执行
|
||||
|
||||
/**
|
||||
* 执行校验函数 (无参)
|
||||
* @param fun 要执行的函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff check(SaFunction fun) {
|
||||
if(isHit) {
|
||||
fun.run();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行校验函数 (带参)
|
||||
* @param fun 要执行的函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff check(SaParamFunction<SaRouterStaff> fun) {
|
||||
if(isHit) {
|
||||
fun.run(this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自由匹配 ( 在free作用域里执行stop()不会跳出Auth函数,而是仅仅跳出free代码块 )
|
||||
* @param fun 要执行的函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff free(SaParamFunction<SaRouterStaff> fun) {
|
||||
if(isHit) {
|
||||
try {
|
||||
fun.run(this);
|
||||
} catch (StopMatchException e) {
|
||||
// 跳出 free自由匹配代码块
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ----------------- 直接指定check函数
|
||||
|
||||
/**
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param fun 要执行的校验方法
|
||||
*/
|
||||
public SaRouterStaff match(String pattern, SaFunction fun) {
|
||||
return this.match(pattern).check(fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配,如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param fun 要执行的校验方法
|
||||
*/
|
||||
public SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
|
||||
return this.match(pattern).check(fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param excludePattern 要排除的路由匹配符
|
||||
* @param fun 要执行的方法
|
||||
*/
|
||||
public SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
|
||||
return this.match(pattern).notMatch(excludePattern).check(fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配 (并指定排除匹配符),如果匹配成功则执行认证函数
|
||||
* @param pattern 路由匹配符
|
||||
* @param excludePattern 要排除的路由匹配符
|
||||
* @param fun 要执行的方法
|
||||
*/
|
||||
public SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
|
||||
return this.match(pattern).notMatch(excludePattern).check(fun);
|
||||
}
|
||||
|
||||
|
||||
// ----------------- 提前退出
|
||||
|
||||
/**
|
||||
* 停止匹配,跳出函数 (在多个匹配链中一次性跳出Auth函数)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff stop() {
|
||||
if(isHit) {
|
||||
throw new StopMatchException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止匹配,结束执行,向前端返回结果
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaRouterStaff back() {
|
||||
if(isHit) {
|
||||
throw new BackResultException("");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止匹配,结束执行,向前端返回结果
|
||||
* @return 对象自身
|
||||
* @param result 要输出的结果
|
||||
*/
|
||||
public SaRouterStaff back(Object result) {
|
||||
if(isHit) {
|
||||
throw new BackResultException(result);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -20,7 +20,17 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
public class SaSession implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 在 Session 上存储角色时建议使用的key
|
||||
*/
|
||||
public static final String ROLE_LIST = "ROLE_LIST";
|
||||
|
||||
/**
|
||||
* 在 Session 上存储权限时建议使用的key
|
||||
*/
|
||||
public static final String PERMISSION_LIST = "PERMISSION_LIST";
|
||||
|
||||
/** 此Session的id */
|
||||
private String id;
|
||||
|
||||
@@ -139,6 +149,16 @@ public class SaSession implements Serializable {
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个token签名
|
||||
*
|
||||
* @param tokenValue token值
|
||||
* @param device 设备标识
|
||||
*/
|
||||
public void addTokenSign(String tokenValue, String device) {
|
||||
addTokenSign(new TokenSign(tokenValue, device));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个token签名
|
||||
*
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
package cn.dev33.satoken.session;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
/**
|
||||
* 自定义Session工具类
|
||||
* 自定义 Session 工具类
|
||||
*
|
||||
* <p>样例:
|
||||
* <pre>
|
||||
* // 在一处代码写入数据
|
||||
* SaSession session = SaSessionCustomUtil.getSessionById("role-" + 1001);
|
||||
* session.set("count", 1);
|
||||
*
|
||||
* // 在另一处代码获取数据
|
||||
* SaSession session = SaSessionCustomUtil.getSessionById("role-" + 1001);
|
||||
* int count = session.getInt("count");
|
||||
* System.out.println("count=" + count);
|
||||
* </pre>
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
@@ -11,12 +24,12 @@ import cn.dev33.satoken.SaManager;
|
||||
public class SaSessionCustomUtil {
|
||||
|
||||
/**
|
||||
* 添加上指定前缀,防止恶意伪造Session
|
||||
* 添加上指定前缀,防止恶意伪造Session
|
||||
*/
|
||||
public static String sessionKey = "custom";
|
||||
|
||||
/**
|
||||
* 拼接Key: 自定义Session的Id
|
||||
* 拼接Key: 自定义Session的Id
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
* @return sessionId
|
||||
@@ -45,7 +58,7 @@ public class SaSessionCustomUtil {
|
||||
public static SaSession getSessionById(String sessionId, boolean isCreate) {
|
||||
SaSession session = SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId));
|
||||
if (session == null && isCreate) {
|
||||
session = SaManager.getSaTokenAction().createSession(splicingSessionKey(sessionId));
|
||||
session = SaStrategy.me.createSession.apply(splicingSessionKey(sessionId));
|
||||
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
|
||||
}
|
||||
return session;
|
||||
|
||||
@@ -3,7 +3,7 @@ package cn.dev33.satoken.session;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Token签名 Model
|
||||
* Token 签名 Model
|
||||
*
|
||||
* 挂在到SaSession上的token签名
|
||||
*
|
||||
@@ -43,7 +43,7 @@ public class TokenSign implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return token value
|
||||
* @return token值
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO模块相关常量
|
||||
* Sa-Token-SSO模块相关常量
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@@ -22,7 +22,7 @@ public class SaSsoConsts {
|
||||
/** SSO-Server端:校验ticket 获取账号id */
|
||||
public static String ssoCheckTicket = "/sso/checkTicket";
|
||||
|
||||
/** SSO-Server端 (and Client端):单点注销 */
|
||||
/** SSO-Server端 (and Client端):单点注销地址 */
|
||||
public static String ssoLogout = "/sso/logout";
|
||||
|
||||
/** SSO-Client端:登录地址 */
|
||||
@@ -48,6 +48,9 @@ public class SaSsoConsts {
|
||||
/** back参数名称 */
|
||||
public static String back = "back";
|
||||
|
||||
/** mode参数名称 */
|
||||
public static String mode = "mode";
|
||||
|
||||
/** loginId参数名称 */
|
||||
public static String loginId = "loginId";
|
||||
|
||||
@@ -57,6 +60,9 @@ public class SaSsoConsts {
|
||||
/** Client端单点注销时-回调URL 参数名称 */
|
||||
public static String ssoLogoutCall = "ssoLogoutCall";
|
||||
|
||||
public static String name = "name";
|
||||
public static String pwd = "pwd";
|
||||
|
||||
}
|
||||
|
||||
/** Client端单点注销回调URL的Set集合,存储在Session中使用的key */
|
||||
@@ -65,7 +71,16 @@ public class SaSsoConsts {
|
||||
/** 表示OK的返回结果 */
|
||||
public static final String OK = "ok";
|
||||
|
||||
/** 表示自己 */
|
||||
public static final String SELF = "self";
|
||||
|
||||
/** 表示简单模式(SSO模式一) */
|
||||
public static final String MODE_SIMPLE = "simple";
|
||||
|
||||
/** 表示ticket模式(SSO模式二和模式三) */
|
||||
public static final String MODE_TICKET = "ticket";
|
||||
|
||||
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
|
||||
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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.router.SaRouter;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.Api;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
@@ -20,59 +20,40 @@ import cn.dev33.satoken.util.SaResult;
|
||||
public class SaSsoHandle {
|
||||
|
||||
/**
|
||||
* 处理Server端请求
|
||||
* 处理Server端所有请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object serverRequest() {
|
||||
|
||||
// 获取变量
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig sso = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// ------------------ 路由分发 ------------------
|
||||
|
||||
// ---------- SSO-Server端:单点登录授权地址
|
||||
if(match(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端:授权地址
|
||||
if(req.isPath(Api.ssoAuth)) {
|
||||
return ssoAuth();
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:RestAPI 登录接口
|
||||
if(match(Api.ssoDoLogin)) {
|
||||
return sso.doLoginHandle.apply(req.getParam("name"), req.getParam("pwd"));
|
||||
// SSO-Server端:RestAPI 登录接口
|
||||
if(req.isPath(Api.ssoDoLogin)) {
|
||||
return ssoDoLogin();
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:校验ticket 获取账号id
|
||||
if(match(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端:校验ticket 获取账号id
|
||||
if(req.isPath(Api.ssoCheckTicket) && cfg.isHttp) {
|
||||
return ssoCheckTicket();
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:单点注销
|
||||
if(match(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;
|
||||
// SSO-Server端:单点注销 [模式一] (不带loginId参数)
|
||||
if(req.isPath(Api.ssoLogout) && cfg.isSlo && req.hasParam(ParamName.loginId) == false) {
|
||||
return ssoServerLogoutType1();
|
||||
}
|
||||
|
||||
// SSO-Server端:单点注销 [模式三] (带loginId参数)
|
||||
if(req.isPath(Api.ssoLogout) && cfg.isHttp && cfg.isSlo && req.hasParam(ParamName.loginId)) {
|
||||
return ssoServerLogout();
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
@@ -80,98 +61,147 @@ public class SaSsoHandle {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Client端请求
|
||||
* SSO-Server端:授权地址
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoAuth() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// ---------- 此处有两种情况分开处理:
|
||||
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
|
||||
if(stpLogic.isLogin() == false) {
|
||||
return cfg.notLoginView.get();
|
||||
}
|
||||
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
|
||||
String mode = req.getParam(ParamName.mode, "");
|
||||
|
||||
// 方式1:直接重定向回Client端 (mode=simple)
|
||||
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
|
||||
String redirect = req.getParam(ParamName.redirect);
|
||||
SaSsoUtil.checkRedirectUrl(redirect);
|
||||
return res.redirect(redirect);
|
||||
} else {
|
||||
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
|
||||
return res.redirect(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:RestAPI 登录接口
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoDoLogin() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// 处理
|
||||
return cfg.doLoginHandle.apply(req.getParam(ParamName.name), req.getParam(ParamName.pwd));
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:校验ticket 获取账号id
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoCheckTicket() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
|
||||
// 获取参数
|
||||
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端:单点注销 [模式一]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoServerLogoutType1() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 开始处理
|
||||
stpLogic.logout();
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Server端:单点注销 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoServerLogout() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
|
||||
// 遍历通知Client端注销会话
|
||||
// step.1 校验秘钥
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
|
||||
// step.2 遍历通知Client端注销会话
|
||||
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.sendHttp.apply(url));
|
||||
|
||||
// step.3 Server端注销
|
||||
stpLogic.logout(loginId);
|
||||
|
||||
// 完成
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理Client端所有请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object clientRequest() {
|
||||
|
||||
// 获取变量
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig sso = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
|
||||
// ------------------ 路由分发 ------------------
|
||||
|
||||
// ---------- SSO-Client端:登录地址
|
||||
if(match(Api.ssoLogin)) {
|
||||
String back = req.getParam(ParamName.back, "/");
|
||||
String ticket = req.getParam(ParamName.ticket);
|
||||
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(stpLogic.isLogin()) {
|
||||
return res.redirect(back);
|
||||
}
|
||||
/*
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从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);
|
||||
}
|
||||
}
|
||||
if(req.isPath(Api.ssoLogin)) {
|
||||
return ssoLogin();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式二]
|
||||
if(match(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, "/"));
|
||||
}
|
||||
if(req.isPath(Api.ssoLogout) && cfg.isSlo && cfg.isHttp == false) {
|
||||
return ssoLogoutType2();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式三]
|
||||
if(match(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("单点注销失败");
|
||||
if(req.isPath(Api.ssoLogout) && cfg.isSlo && cfg.isHttp) {
|
||||
return ssoLogoutType3();
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销的回调 [模式三]
|
||||
if(match(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;
|
||||
if(req.isPath(Api.ssoLogoutCall) && cfg.isSlo && cfg.isHttp) {
|
||||
return ssoLogoutCall();
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
@@ -179,12 +209,159 @@ public class SaSsoHandle {
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配算法
|
||||
* @param pattern 路由表达式
|
||||
* @return 是否可以匹配
|
||||
* SSO-Client端:登录地址
|
||||
* @return 处理结果
|
||||
*/
|
||||
static boolean match(String pattern) {
|
||||
return SaRouter.isMatchCurrURI(pattern);
|
||||
public static Object ssoLogin() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String back = req.getParam(ParamName.back, "/");
|
||||
String ticket = req.getParam(ParamName.ticket);
|
||||
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(stpLogic.isLogin()) {
|
||||
return res.redirect(back);
|
||||
}
|
||||
/*
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return res.redirect(serverAuthUrl);
|
||||
} else {
|
||||
// ------- 1、校验ticket,获取账号id
|
||||
Object loginId = checkTicket(ticket, Api.ssoLogin);
|
||||
|
||||
// Be: 如果开发者自定义了处理逻辑
|
||||
if(cfg.ticketResultHandle != null) {
|
||||
return cfg.ticketResultHandle.apply(loginId, back);
|
||||
}
|
||||
// ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址
|
||||
if(loginId != null ) {
|
||||
stpLogic.login(loginId);
|
||||
return res.redirect(back);
|
||||
} else {
|
||||
// 如果ticket无效:
|
||||
throw new SaTokenException("无效ticket:" + ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销 [模式二]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutType2() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 开始处理
|
||||
stpLogic.logout();
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutType3() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 如果未登录,则无需注销
|
||||
if(stpLogic.isLogin() == false) {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 调用SSO-Server认证中心API,进行注销
|
||||
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
|
||||
String body = String.valueOf(cfg.sendHttp.apply(url));
|
||||
if(SaSsoConsts.OK.equals(body) == false) {
|
||||
return SaResult.error("单点注销失败");
|
||||
}
|
||||
|
||||
// 返回
|
||||
return ssoLogoutBack(req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO-Client端:单点注销的回调 [模式三]
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object ssoLogoutCall() {
|
||||
// 获取对象
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
|
||||
|
||||
// 获取参数
|
||||
String loginId = req.getParam(ParamName.loginId);
|
||||
String secretkey = req.getParam(ParamName.secretkey);
|
||||
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装:单点注销成功后返回结果
|
||||
* @param req SaRequest对象
|
||||
* @param res SaResponse对象
|
||||
* @return 返回结果
|
||||
*/
|
||||
public static Object ssoLogoutBack(SaRequest req, SaResponse res) {
|
||||
/*
|
||||
* 三种情况:
|
||||
* 1. 有back参数,值为SELF -> 回退一级并刷新
|
||||
* 2. 有back参数,值为url -> 跳转到此url地址
|
||||
* 3. 无back参数 -> 返回json数据
|
||||
*/
|
||||
String back = req.getParam(ParamName.back);
|
||||
if(SaFoxUtil.isNotEmpty(back)) {
|
||||
if(back.equals(SaSsoConsts.SELF)) {
|
||||
return "<script>if(document.referrer != location.href){ location.replace(document.referrer || '/'); }</script>";
|
||||
}
|
||||
return res.redirect(back);
|
||||
} else {
|
||||
return SaResult.ok("单点注销成功");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装:校验ticket,取出loginId
|
||||
* @param ticket ticket码
|
||||
* @param currUri 当前路由的uri,用于计算单点注销回调地址
|
||||
* @return loginId
|
||||
*/
|
||||
public static Object checkTicket(String ticket, String currUri) {
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
// --------- 两种模式
|
||||
if(cfg.isHttp) {
|
||||
// 模式三:使用http请求校验ticket
|
||||
String ssoLogoutCall = null;
|
||||
if(cfg.isSlo) {
|
||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall);
|
||||
}
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
Object body = cfg.sendHttp.apply(checkUrl);
|
||||
return (SaFoxUtil.isEmpty(body) ? null : body);
|
||||
} else {
|
||||
// 模式二:直连Redis校验ticket
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,77 +8,88 @@ 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.session.SaSession;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录接口
|
||||
* Sa-Token-SSO 单点登录模块
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoTemplate {
|
||||
|
||||
/**
|
||||
* 单点登录模块使用的 StpLogic 对象
|
||||
*/
|
||||
public StpLogic stpLogic;
|
||||
public SaSsoTemplate(StpLogic stpLogic) {
|
||||
this.stpLogic = stpLogic;
|
||||
}
|
||||
|
||||
// ---------------------- Ticket 操作 ----------------------
|
||||
|
||||
/**
|
||||
* 创建一个 Ticket码
|
||||
* 根据 账号id 创建一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return 票据
|
||||
* @return Ticket码
|
||||
*/
|
||||
public String createTicket(Object loginId) {
|
||||
// 随机一个ticket
|
||||
// 创建 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);
|
||||
// 保存 Ticket
|
||||
saveTicket(ticket, loginId);
|
||||
saveTicketIndex(ticket, loginId);
|
||||
|
||||
// 返回
|
||||
// 返回 Ticket
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Ticket码
|
||||
* @param ticket Ticket码
|
||||
* 保存 Ticket
|
||||
* @param ticket ticket码
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void deleteTicket(String ticket) {
|
||||
Object loginId = getLoginId(ticket);
|
||||
if(loginId != null) {
|
||||
SaManager.getSaTokenDao().delete(splicingKeyTicketToId(ticket));
|
||||
SaManager.getSaTokenDao().delete(splicingKeyIdToTicket(loginId));
|
||||
}
|
||||
public void saveTicket(String ticket, Object loginId) {
|
||||
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
|
||||
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticke的地址
|
||||
* 保存 Ticket 索引
|
||||
* @param ticket ticket码
|
||||
* @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;
|
||||
public void saveTicketIndex(String ticket, Object loginId) {
|
||||
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
|
||||
SaManager.getSaTokenDao().set(splicingTicketIndexKey(loginId), String.valueOf(ticket), ticketTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public void deleteTicket(String ticket) {
|
||||
if(ticket == null) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().delete(splicingTicketSaveKey(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Ticket索引
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void deleteTicketIndex(Object loginId) {
|
||||
if(loginId == null) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().delete(splicingTicketIndexKey(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
@@ -88,7 +99,7 @@ public class SaSsoTemplate {
|
||||
if(SaFoxUtil.isEmpty(ticket)) {
|
||||
return null;
|
||||
}
|
||||
return SaManager.getSaTokenDao().get(splicingKeyTicketToId(ticket));
|
||||
return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,9 +112,21 @@ public class SaSsoTemplate {
|
||||
public <T> T getLoginId(String ticket, Class<T> cs) {
|
||||
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除
|
||||
* 查询 指定账号id的 Ticket值
|
||||
* @param loginId 账号id
|
||||
* @return Ticket值
|
||||
*/
|
||||
public String getTicketValue(Object loginId) {
|
||||
if(loginId == null) {
|
||||
return null;
|
||||
}
|
||||
return SaManager.getSaTokenDao().get(splicingTicketIndexKey(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
@@ -111,45 +134,31 @@ public class SaSsoTemplate {
|
||||
Object loginId = getLoginId(ticket);
|
||||
if(loginId != null) {
|
||||
deleteTicket(ticket);
|
||||
deleteTicketIndex(loginId);
|
||||
}
|
||||
return loginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
* 随机一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return Ticket码
|
||||
*/
|
||||
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;
|
||||
public String randomTicket(Object loginId) {
|
||||
return SaFoxUtil.getRandomString(64);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 单点登录地址
|
||||
* 构建URL:Server端 单点登录地址
|
||||
* @param clientLoginUrl Client端登录地址
|
||||
* @param back 回调路径
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
*/
|
||||
public String buildServerAuthUrl(String clientLoginUrl, String back) {
|
||||
|
||||
// 服务端认证地址
|
||||
String serverUrl = SaManager.getConfig().getSso().getAuthUrl();
|
||||
|
||||
@@ -165,6 +174,63 @@ public class SaSsoTemplate {
|
||||
return serverAuthUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticke的地址
|
||||
* @param loginId 账号id
|
||||
* @param redirect Client端提供的重定向地址
|
||||
* @return see note
|
||||
*/
|
||||
public String buildRedirectUrl(Object loginId, String redirect) {
|
||||
|
||||
// 校验 重定向地址 是否合法
|
||||
checkRedirectUrl(redirect);
|
||||
|
||||
// 删掉 旧Ticket
|
||||
deleteTicket(getTicketValue(loginId));
|
||||
|
||||
// 创建 新Ticket
|
||||
String ticket = createTicket(loginId);
|
||||
|
||||
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket)
|
||||
return SaFoxUtil.joinParam(encodeBackParam(redirect), ParamName.ticket, ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public void checkRedirectUrl(String url) {
|
||||
|
||||
// 1、是否是一个有效的url
|
||||
if(SaFoxUtil.isUrl(url) == false) {
|
||||
throw new SaTokenException("无效redirect:" + url);
|
||||
}
|
||||
|
||||
// 2、截取掉?后面的部分
|
||||
int qIndex = url.indexOf("?");
|
||||
if(qIndex != -1) {
|
||||
url = url.substring(0, qIndex);
|
||||
}
|
||||
|
||||
// 3、是否在[允许地址列表]之中
|
||||
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
|
||||
if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) {
|
||||
throw new SaTokenException("非法redirect:" + url);
|
||||
}
|
||||
|
||||
// 校验通过 √
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
// 默认从配置文件中返回
|
||||
return SaManager.getConfig().getSso().getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
|
||||
* @param url url
|
||||
@@ -192,16 +258,21 @@ public class SaSsoTemplate {
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return 票据
|
||||
* 构建URL:Server端 账号资料查询地址
|
||||
* @param loginId 账号id
|
||||
* @return Server端 账号资料查询地址
|
||||
*/
|
||||
public String randomTicket(Object loginId) {
|
||||
return SaFoxUtil.getRandomString(64);
|
||||
public String buildUserinfoUrl(Object loginId) {
|
||||
// 拼接
|
||||
String userinfoUrl = SaManager.getConfig().getSso().getUserinfoUrl();
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.loginId, loginId);
|
||||
userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.secretkey, SaManager.getConfig().getSso().getSecretkey());
|
||||
// 返回
|
||||
return userinfoUrl;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- SSO 模式三 -------------------
|
||||
|
||||
// ------------------- SSO 模式三相关 -------------------
|
||||
|
||||
/**
|
||||
* 校验secretkey秘钥是否有效
|
||||
@@ -215,18 +286,23 @@ public class SaSsoTemplate {
|
||||
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* <p> 在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id
|
||||
* @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;
|
||||
}
|
||||
@@ -240,9 +316,10 @@ public class SaSsoTemplate {
|
||||
if(loginId == null || sloCallbackUrl == null || sloCallbackUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet<String>());
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId);
|
||||
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet<String>());
|
||||
urlSet.add(sloCallbackUrl);
|
||||
stpLogic.getSessionByLoginId(loginId).set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
|
||||
session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,14 +328,17 @@ public class SaSsoTemplate {
|
||||
* @param fun 调用方法
|
||||
*/
|
||||
public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
|
||||
SaSession session = stpLogic.getSessionByLoginId(loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String secretkey = SaManager.getConfig().getSso().getSecretkey();
|
||||
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY,
|
||||
() -> new HashSet<String>());
|
||||
|
||||
Set<String> urlSet = session.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);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
|
||||
// 调用
|
||||
fun.run(url);
|
||||
}
|
||||
@@ -295,6 +375,16 @@ public class SaSsoTemplate {
|
||||
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public Object getUserinfo(Object loginId) {
|
||||
String url = buildUserinfoUrl(loginId);
|
||||
return SaManager.getConfig().getSso().sendHttp.apply(url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 返回相应key -------------------
|
||||
@@ -304,22 +394,25 @@ public class SaSsoTemplate {
|
||||
* @param ticket ticket值
|
||||
* @return key
|
||||
*/
|
||||
public String splicingKeyTicketToId(String ticket) {
|
||||
public String splicingTicketSaveKey(String ticket) {
|
||||
return SaManager.getConfig().getTokenName() + ":ticket:" + ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key:账号Id 反查 Ticket
|
||||
* 拼接key:账号Id 反查 Ticket
|
||||
* @param id 账号id
|
||||
* @return key
|
||||
*/
|
||||
public String splicingKeyIdToTicket(Object id) {
|
||||
public String splicingTicketIndexKey(Object id) {
|
||||
return SaManager.getConfig().getTokenName() + ":id-ticket:" + id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 单点注销回调函数
|
||||
* @author kong
|
||||
*/
|
||||
@FunctionalInterface
|
||||
static interface CallSloUrlFunction{
|
||||
public static interface CallSloUrlFunction{
|
||||
/**
|
||||
* 调用function
|
||||
* @param url 注销回调URL
|
||||
@@ -327,5 +420,4 @@ public class SaSsoTemplate {
|
||||
public void run(String url);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录工具类
|
||||
* Sa-Token-SSO 单点登录模块 工具类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@@ -15,33 +15,34 @@ public class SaSsoUtil {
|
||||
*/
|
||||
public static SaSsoTemplate saSsoTemplate = new SaSsoTemplate(StpUtil.stpLogic);
|
||||
|
||||
|
||||
// ---------------------- Ticket 操作 ----------------------
|
||||
|
||||
/**
|
||||
* 创建一个 Ticket票据
|
||||
* 根据 账号id 创建一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return 票据
|
||||
* @return Ticket码
|
||||
*/
|
||||
public static String createTicket(Object loginId) {
|
||||
return saSsoTemplate.createTicket(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Ticket码
|
||||
* 删除 Ticket
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public static void deleteTicket(String ticket) {
|
||||
saSsoTemplate.deleteTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向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索引
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void deleteTicketIndex(Object loginId) {
|
||||
saSsoTemplate.deleteTicketIndex(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
@@ -63,7 +64,7 @@ public class SaSsoUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除
|
||||
* 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
@@ -71,13 +72,8 @@ public class SaSsoUtil {
|
||||
return saSsoTemplate.checkTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public static void checkAuthUrl(String url) {
|
||||
saSsoTemplate.checkRedirectUrl(url);
|
||||
}
|
||||
|
||||
// ---------------------- 构建URL ----------------------
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 单点登录地址
|
||||
@@ -89,6 +85,40 @@ public class SaSsoUtil {
|
||||
return saSsoTemplate.buildServerAuthUrl(clientLoginUrl, back);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端向Client下放ticke的地址
|
||||
* @param loginId 账号id
|
||||
* @param redirect Client端提供的重定向地址
|
||||
* @return see note
|
||||
*/
|
||||
public static String buildRedirectUrl(Object loginId, String redirect) {
|
||||
return saSsoTemplate.buildRedirectUrl(loginId, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验重定向url合法性
|
||||
* @param url 下放ticket的url地址
|
||||
*/
|
||||
public static void checkRedirectUrl(String url) {
|
||||
saSsoTemplate.checkRedirectUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return see note
|
||||
*/
|
||||
public static String getAllowUrl() {
|
||||
return saSsoTemplate.getAllowUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL:Server端 账号资料查询地址
|
||||
* @param loginId 账号id
|
||||
* @return Server端 账号资料查询地址
|
||||
*/
|
||||
public static String buildUserinfoUrl(Object loginId) {
|
||||
return saSsoTemplate.buildUserinfoUrl(loginId);
|
||||
}
|
||||
|
||||
// ------------------- SSO 模式三 -------------------
|
||||
|
||||
@@ -147,4 +177,13 @@ public class SaSsoUtil {
|
||||
saSsoTemplate.singleLogout(secretkey, loginId, fun);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:账号资料
|
||||
* @param loginId 账号id
|
||||
* @return 账号资料
|
||||
*/
|
||||
public static Object getUserinfo(Object loginId) {
|
||||
return saSsoTemplate.getUserinfo(loginId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import cn.dev33.satoken.util.SaTokenConsts;
|
||||
*/
|
||||
public class SaLoginModel {
|
||||
|
||||
|
||||
/**
|
||||
* 此次登录的客户端设备标识
|
||||
*/
|
||||
@@ -91,6 +90,16 @@ public class SaLoginModel {
|
||||
return (int)(long)timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获取device参数,如果为null,则返回默认值
|
||||
*/
|
||||
public String getDeviceOrDefalut() {
|
||||
if(device == null) {
|
||||
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建对象,初始化默认值
|
||||
* @return 对象自身
|
||||
@@ -105,9 +114,9 @@ public class SaLoginModel {
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel build(SaTokenConfig config) {
|
||||
if(device == null) {
|
||||
device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
}
|
||||
// if(device == null) {
|
||||
// device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
// }
|
||||
if(isLastingCookie == null) {
|
||||
isLastingCookie = true;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package cn.dev33.satoken.stp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限验证工具类
|
||||
* Sa-Token 权限认证工具类
|
||||
* @author kong
|
||||
*/
|
||||
public class StpUtil {
|
||||
@@ -29,6 +30,16 @@ public class StpUtil {
|
||||
return stpLogic.getLoginType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 StpLogic 对象
|
||||
* @param stpLogic /
|
||||
*/
|
||||
public static void setStpLogic(StpLogic stpLogic) {
|
||||
StpUtil.stpLogic = stpLogic;
|
||||
// 防止自定义 stpLogic 被覆盖
|
||||
SaManager.putStpLogic(stpLogic);
|
||||
}
|
||||
|
||||
|
||||
// =================== 获取token 相关 ===================
|
||||
|
||||
@@ -68,6 +79,8 @@ public class StpUtil {
|
||||
|
||||
// =================== 登录相关操作 ===================
|
||||
|
||||
// --- 登录
|
||||
|
||||
/**
|
||||
* 会话登录
|
||||
* @param id 账号id,建议的类型:(long | int | String)
|
||||
@@ -102,16 +115,37 @@ public class StpUtil {
|
||||
public static void login(Object id, SaLoginModel loginModel) {
|
||||
stpLogic.login(id, loginModel);
|
||||
}
|
||||
|
||||
// --- 注销
|
||||
|
||||
/**
|
||||
* 会话注销
|
||||
* 会话注销
|
||||
*/
|
||||
public static void logout() {
|
||||
stpLogic.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定Token
|
||||
* 会话注销,根据账号id
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logout(Object loginId) {
|
||||
stpLogic.logout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备标识
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表所有注销设备)
|
||||
*/
|
||||
public static void logout(Object loginId, String device) {
|
||||
stpLogic.logout(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定 Token
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
*/
|
||||
public static void logoutByTokenValue(String tokenValue) {
|
||||
@@ -119,24 +153,48 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
|
||||
* 踢人下线,根据账号id
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId) {
|
||||
stpLogic.logoutByLoginId(loginId);
|
||||
public static void kickout(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id 和 设备标识
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表踢出所有设备)
|
||||
*/
|
||||
public static void kickout(Object loginId, String device) {
|
||||
stpLogic.kickout(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id and 设备标识 (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识
|
||||
* 踢人下线,根据指定 Token
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId, String device) {
|
||||
stpLogic.logoutByLoginId(loginId, device);
|
||||
public static void kickoutByTokenValue(String tokenValue) {
|
||||
stpLogic.kickoutByTokenValue(tokenValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 顶人下线,根据账号id 和 设备标识
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表顶替所有设备)
|
||||
*/
|
||||
public static void replaced(Object loginId, String device) {
|
||||
stpLogic.replaced(loginId, device);
|
||||
}
|
||||
|
||||
|
||||
// 查询相关
|
||||
|
||||
/**
|
||||
@@ -214,7 +272,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
|
||||
// =================== session相关 ===================
|
||||
// =================== User-Session 相关 ===================
|
||||
|
||||
/**
|
||||
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
|
||||
@@ -262,7 +320,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
|
||||
// =================== token专属session ===================
|
||||
// =================== Token-Session 相关 ===================
|
||||
|
||||
/**
|
||||
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
|
||||
@@ -304,7 +362,7 @@ public class StpUtil {
|
||||
// =================== 过期时间相关 ===================
|
||||
|
||||
/**
|
||||
* 获取当前登录者的token剩余有效时间 (单位: 秒)
|
||||
* 获取当前登录者的 token 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getTokenTimeout() {
|
||||
@@ -312,7 +370,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录者的Session剩余有效时间 (单位: 秒)
|
||||
* 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getSessionTimeout() {
|
||||
@@ -320,7 +378,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前token的专属Session剩余有效时间 (单位: 秒)
|
||||
* 获取当前 Token-Session 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getTokenSessionTimeout() {
|
||||
@@ -328,8 +386,8 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前token[临时过期]剩余有效时间 (单位: 秒)
|
||||
* @return token[临时过期]剩余有效时间
|
||||
* 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
|
||||
* @return token [临时过期] 剩余有效时间
|
||||
*/
|
||||
public static long getTokenActivityTimeout() {
|
||||
return stpLogic.getTokenActivityTimeout();
|
||||
@@ -339,8 +397,34 @@ public class StpUtil {
|
||||
|
||||
// =================== 角色验证操作 ===================
|
||||
|
||||
/**
|
||||
* 获取:当前账号的角色集合
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getRoleList() {
|
||||
return stpLogic.getRoleList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:指定账号的角色集合
|
||||
* @param loginId 指定账号id
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getRoleList(Object loginId) {
|
||||
return stpLogic.getRoleList(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号id是否含有角色标识, 返回true或false
|
||||
* 判断:当前账号是否拥有指定角色, 返回true或false
|
||||
* @param role 角色标识
|
||||
* @return 是否含有指定角色标识
|
||||
*/
|
||||
public static boolean hasRole(String role) {
|
||||
return stpLogic.hasRole(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定账号是否含有指定角色标识, 返回true或false
|
||||
* @param loginId 账号id
|
||||
* @param role 角色标识
|
||||
* @return 是否含有指定角色标识
|
||||
@@ -350,16 +434,25 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识, 返回true或false
|
||||
* @param role 角色标识
|
||||
* @return 是否含有指定角色标识
|
||||
* 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* @param roleArray 角色标识数组
|
||||
* @return true或false
|
||||
*/
|
||||
public static boolean hasRole(String role) {
|
||||
return stpLogic.hasRole(role);
|
||||
public static boolean hasRoleAnd(String... roleArray){
|
||||
return stpLogic.hasRoleAnd(roleArray);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
|
||||
* 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* @param roleArray 角色标识数组
|
||||
* @return true或false
|
||||
*/
|
||||
public static boolean hasRoleOr(String... roleArray){
|
||||
return stpLogic.hasRoleOr(roleArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
|
||||
* @param role 角色标识
|
||||
*/
|
||||
public static void checkRole(String role) {
|
||||
@@ -367,7 +460,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* @param roleArray 角色标识数组
|
||||
*/
|
||||
public static void checkRoleAnd(String... roleArray){
|
||||
@@ -375,18 +468,44 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* @param roleArray 角色标识数组
|
||||
*/
|
||||
public static void checkRoleOr(String... roleArray){
|
||||
stpLogic.checkRoleOr(roleArray);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =================== 权限验证操作 ===================
|
||||
|
||||
/**
|
||||
* 获取:当前账号的权限码集合
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getPermissionList() {
|
||||
return stpLogic.getPermissionList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:指定账号的权限码集合
|
||||
* @param loginId 指定账号id
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getPermissionList(Object loginId) {
|
||||
return stpLogic.getPermissionList(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定账号id是否含有指定权限, 返回true或false
|
||||
* 判断:当前账号是否含有指定权限, 返回true或false
|
||||
* @param permission 权限码
|
||||
* @return 是否含有指定权限
|
||||
*/
|
||||
public static boolean hasPermission(String permission) {
|
||||
return stpLogic.hasPermission(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定账号id是否含有指定权限, 返回true或false
|
||||
* @param loginId 账号id
|
||||
* @param permission 权限码
|
||||
* @return 是否含有指定权限
|
||||
@@ -396,16 +515,25 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限, 返回true或false
|
||||
* @param permission 权限码
|
||||
* @return 是否含有指定权限
|
||||
* 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
|
||||
* @param permissionArray 权限码数组
|
||||
* @return true 或 false
|
||||
*/
|
||||
public static boolean hasPermission(String permission) {
|
||||
return stpLogic.hasPermission(permission);
|
||||
}
|
||||
public static boolean hasPermissionAnd(String... permissionArray){
|
||||
return stpLogic.hasPermissionAnd(permissionArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||
* 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* @param permissionArray 权限码数组
|
||||
* @return true 或 false
|
||||
*/
|
||||
public static boolean hasPermissionOr(String... permissionArray){
|
||||
return stpLogic.hasPermissionOr(permissionArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||
* @param permission 权限码
|
||||
*/
|
||||
public static void checkPermission(String permission) {
|
||||
@@ -413,7 +541,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
|
||||
* 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
|
||||
* @param permissionArray 权限码数组
|
||||
*/
|
||||
public static void checkPermissionAnd(String... permissionArray) {
|
||||
@@ -421,7 +549,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* @param permissionArray 权限码数组
|
||||
*/
|
||||
public static void checkPermissionOr(String... permissionArray) {
|
||||
@@ -637,6 +765,7 @@ public class StpUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 获取当前StpLogin的loginKey
|
||||
* @return 当前StpLogin的loginKey
|
||||
*/
|
||||
@@ -647,6 +776,7 @@ public class StpUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
*/
|
||||
@@ -657,6 +787,7 @@ public class StpUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param device 设备标识
|
||||
@@ -668,6 +799,7 @@ public class StpUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param isLastingCookie 是否为持久Cookie
|
||||
@@ -679,6 +811,7 @@ public class StpUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定所有登录参数Model
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param loginModel 此次登录的参数Model
|
||||
@@ -688,4 +821,29 @@ public class StpUtil {
|
||||
stpLogic.login(loginId, loginModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
@Deprecated
|
||||
public static void logoutByLoginId(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id and 设备标识 (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表所有注销设备)
|
||||
*/
|
||||
@Deprecated
|
||||
public static void logoutByLoginId(Object loginId, String device) {
|
||||
stpLogic.kickout(loginId, device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package cn.dev33.satoken.strategy;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
/**
|
||||
* Sa-Token 策略对象
|
||||
* <p>
|
||||
* 此类统一定义框架内的一些关键性逻辑算法,方便开发者进行按需重写,例:
|
||||
* <pre>
|
||||
// SaStrategy全局单例,所有方法都用以下形式重写
|
||||
SaStrategy.me.setCreateToken((loginId, loginType) -> {
|
||||
// 自定义Token生成的算法
|
||||
return "xxxx";
|
||||
});
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @author kong
|
||||
* @param <T>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public final class SaStrategy {
|
||||
|
||||
private SaStrategy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SaStrategy 对象的单例引用
|
||||
*/
|
||||
public static final SaStrategy me = new SaStrategy();
|
||||
|
||||
//
|
||||
// 所有策略
|
||||
//
|
||||
|
||||
/**
|
||||
* 创建 Token 的策略
|
||||
* <p> 参数 [账号id, 账号类型]
|
||||
*/
|
||||
public BiFunction<Object, String, String> createToken = (loginId, loginType) -> {
|
||||
return SaManager.getSaTokenAction().createToken(loginId, loginType);
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建 Session 的策略
|
||||
* <p> 参数 [SessionId]
|
||||
*/
|
||||
public Function<String, SaSession> createSession = (sessionId) -> {
|
||||
return SaManager.getSaTokenAction().createSession(sessionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断:集合中是否包含指定元素(模糊匹配)
|
||||
* <p> 参数 [集合, 元素]
|
||||
*/
|
||||
public BiFunction<List<String>, String, Boolean> hasElement = (list, element) -> {
|
||||
return SaManager.getSaTokenAction().hasElement(list, element);
|
||||
};
|
||||
|
||||
/**
|
||||
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
|
||||
* <p> 参数 [Method句柄]
|
||||
*/
|
||||
public Consumer<Method> checkMethodAnnotation = (method) -> {
|
||||
|
||||
// 先校验 Method 所属 Class 上的注解
|
||||
me.checkElementAnnotation.accept(method.getDeclaringClass());
|
||||
|
||||
// 再校验 Method 上的注解
|
||||
me.checkElementAnnotation.accept(method);
|
||||
};
|
||||
|
||||
/**
|
||||
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
|
||||
* <p> 参数 [element元素]
|
||||
*/
|
||||
public Consumer<AnnotatedElement> checkElementAnnotation = (element) -> {
|
||||
// 为了兼容旧版本
|
||||
SaManager.getSaTokenAction().validateAnnotation(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* 从元素上获取注解(注解鉴权内部实现)
|
||||
* <p> 参数 [element元素,要获取的注解类型]
|
||||
*/
|
||||
public BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation = (element, annotationClass)->{
|
||||
// 默认使用jdk的注解处理器
|
||||
return element.getAnnotation(annotationClass);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// 重写策略 set连缀风格
|
||||
//
|
||||
|
||||
/**
|
||||
* 重写创建 Token 的策略
|
||||
* <p> 参数 [账号id, 账号类型]
|
||||
* @param createToken /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setCreateToken(BiFunction<Object, String, String> createToken) {
|
||||
this.createToken = createToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写创建 Session 的策略
|
||||
* <p> 参数 [SessionId]
|
||||
* @param createSession /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setCreateSession(Function<String, SaSession> createSession) {
|
||||
this.createSession = createSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:集合中是否包含指定元素(模糊匹配)
|
||||
* <p> 参数 [集合, 元素]
|
||||
* @param hasElement /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setHasElement(BiFunction<List<String>, String, Boolean> hasElement) {
|
||||
this.hasElement = hasElement;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
|
||||
* <p> 参数 [Method句柄]
|
||||
* @param checkMethodAnnotation /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setCheckMethodAnnotation(Consumer<Method> checkMethodAnnotation) {
|
||||
this.checkMethodAnnotation = checkMethodAnnotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
|
||||
* <p> 参数 [element元素]
|
||||
* @param checkElementAnnotation /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setCheckElementAnnotation(Consumer<AnnotatedElement> checkElementAnnotation) {
|
||||
this.checkElementAnnotation = checkElementAnnotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从元素上获取注解(注解鉴权内部实现)
|
||||
* <p> 参数 [element元素,要获取的注解类型]
|
||||
* @param getAnnotation /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaStrategy setGetAnnotation(BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation) {
|
||||
this.getAnnotation = getAnnotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.dev33.satoken.temp;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
@@ -19,7 +20,7 @@ public interface SaTempInterface {
|
||||
public default String createToken(Object value, long timeout) {
|
||||
|
||||
// 生成 token
|
||||
String token = SaManager.getSaTokenAction().createToken(null, null);
|
||||
String token = SaStrategy.me.createToken.apply(null, null);
|
||||
|
||||
// 持久化映射关系
|
||||
String key = splicingKeyTempToken(token);
|
||||
@@ -60,6 +61,15 @@ public interface SaTempInterface {
|
||||
String key = splicingKeyTempToken(token);
|
||||
return SaManager.getSaTokenDao().getObjectTimeout(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 token
|
||||
* @param token 指定token
|
||||
*/
|
||||
public default void deleteToken(String token) {
|
||||
String key = splicingKeyTempToken(token);
|
||||
SaManager.getSaTokenDao().deleteObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射关系的持久化key
|
||||
|
||||
@@ -48,5 +48,13 @@ public class SaTempUtil {
|
||||
public static long getTimeout(String token) {
|
||||
return SaManager.getSaTemp().getTimeout(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 token
|
||||
* @param token 指定token
|
||||
*/
|
||||
public static void deleteToken(String token) {
|
||||
SaManager.getSaTemp().deleteToken(token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,15 @@ public class SaFoxUtil {
|
||||
public static boolean isEmpty(Object str) {
|
||||
return str == null || "".equals(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定元素是否不为 (null或者空字符串)
|
||||
* @param str 指定元素
|
||||
* @return 是否为null或者空字符串
|
||||
*/
|
||||
public static boolean isNotEmpty(Object str) {
|
||||
return isEmpty(str) == false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以当前时间戳和随机int数字拼接一个随机字符串
|
||||
@@ -395,4 +404,51 @@ public class SaFoxUtil {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* String 转 Array,按照逗号切割
|
||||
* @param str 字符串
|
||||
* @return 数组
|
||||
*/
|
||||
public static String[] convertStringToArray(String str) {
|
||||
List<String> list = convertStringToList(str);
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array 转 String,按照逗号切割
|
||||
* @param arr 数组
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String convertArrayToString(String[] arr) {
|
||||
if(arr == null || arr.length == 0) {
|
||||
return "";
|
||||
}
|
||||
return String.join(",", arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个空集合
|
||||
* @param <T> 集合类型
|
||||
* @return 空集合
|
||||
*/
|
||||
public static <T>List<T> emptyList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* String数组转集合
|
||||
* @param strs String数组
|
||||
* @return 集合
|
||||
*/
|
||||
public static List<String> toList(String... strs) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String str : strs) {
|
||||
list.add(str);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -109,6 +109,9 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
|
||||
public static SaResult ok(String msg) {
|
||||
return new SaResult(CODE_SUCCESS, msg, null);
|
||||
}
|
||||
public static SaResult code(int code) {
|
||||
return new SaResult(code, null, null);
|
||||
}
|
||||
public static SaResult data(Object data) {
|
||||
return new SaResult(CODE_SUCCESS, "ok", data);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* Sa-Token 当前版本号
|
||||
*/
|
||||
public static final String VERSION_NO = "v1.23.0";
|
||||
public static final String VERSION_NO = "v1.27.0";
|
||||
|
||||
/**
|
||||
* Sa-Token 开源地址
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- springboot依赖 -->
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
@@ -38,14 +38,14 @@
|
||||
<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>
|
||||
|
||||
@@ -15,13 +15,13 @@ sa-token:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
host: 49.235.117.153
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
password: Kdfsjia.d.1212dsa
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
@@ -45,7 +45,7 @@ spring:
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -133,7 +133,7 @@ public class SaTokenJwtUtil {
|
||||
String tokenValue = createTokenValue(loginId);
|
||||
storage.set(splicingKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里
|
||||
if(config.getIsReadCookie() == true){ // cookie注入
|
||||
SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), (int)config.getTimeout());
|
||||
SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookie().getDomain(), (int)config.getTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ConfigurationProperties -->
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<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">
|
||||
@@ -58,7 +59,7 @@
|
||||
<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 置换资源: 获取账号昵称、头像、性别等信息 </span>
|
||||
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) </span>
|
||||
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
|
||||
|
||||
<br>
|
||||
@@ -202,7 +203,11 @@
|
||||
data: {accessToken: accessToken},
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
layer.alert(JSON.stringify(res.data));
|
||||
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));
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -63,7 +63,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ConfigurationProperties -->
|
||||
|
||||
@@ -76,6 +76,9 @@ public class SaOAuth2ServerController {
|
||||
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_");
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-web</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.5.27</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
|
||||
@@ -185,7 +185,7 @@ public class TestController {
|
||||
// 先登录上
|
||||
StpUtil.login(10001);
|
||||
// 踢下线
|
||||
StpUtil.logoutByLoginId(10001);
|
||||
StpUtil.kickout(10001);
|
||||
// 再尝试获取
|
||||
StpUtil.getLoginId();
|
||||
// 返回
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
<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.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -59,11 +60,11 @@
|
||||
</dependency> -->
|
||||
|
||||
<!-- Sa-Token整合SpringAOP实现注解鉴权 -->
|
||||
<dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-aop</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
</dependency> -->
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.pj.current;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableLoginException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ResponseBody
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
e.printStackTrace();
|
||||
|
||||
// 不同异常返回不同状态码
|
||||
AjaxJson aj = null;
|
||||
if (e instanceof NotLoginException) { // 如果是未登录异常
|
||||
NotLoginException ee = (NotLoginException) e;
|
||||
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
|
||||
}
|
||||
else if(e instanceof NotRoleException) { // 如果是角色异常
|
||||
NotRoleException ee = (NotRoleException) e;
|
||||
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
|
||||
}
|
||||
else if(e instanceof NotPermissionException) { // 如果是权限异常
|
||||
NotPermissionException ee = (NotPermissionException) e;
|
||||
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
|
||||
}
|
||||
else if(e instanceof DisableLoginException) { // 如果是被封禁异常
|
||||
DisableLoginException ee = (DisableLoginException) e;
|
||||
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
|
||||
}
|
||||
else { // 普通异常, 输出:500 + 异常信息
|
||||
aj = AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.pj.current;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 处理 404
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class NotFoundHandle implements ErrorController {
|
||||
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return "/error";
|
||||
}
|
||||
|
||||
@RequestMapping("/error")
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
response.setStatus(200);
|
||||
return SaResult.get(404, "not found", null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@@ -10,6 +12,7 @@ import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
|
||||
/**
|
||||
@@ -19,18 +22,18 @@ import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
|
||||
/**
|
||||
* 注册sa-token的拦截器,打开注解式鉴权功能
|
||||
* 注册Sa-Token 的拦截器,打开注解式鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册注解拦截器
|
||||
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**").excludePathPatterns("");
|
||||
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [sa-token全局过滤器]
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
@@ -40,10 +43,9 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(r -> {
|
||||
// System.out.println("---------- sa全局认证");
|
||||
|
||||
// SaRouter.match("/test/test", () -> new Object());
|
||||
.setAuth(obj -> {
|
||||
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
@@ -69,5 +71,15 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重写 Sa-Token 框架内部算法策略
|
||||
*/
|
||||
@Autowired
|
||||
public void rewriteSaStrategy() {
|
||||
// 重写Sa-Token的注解处理器,增加注解合并功能
|
||||
SaStrategy.me.getAnnotation = (element, annotationClass) -> {
|
||||
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.pj.satoken.at;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaCheckSafe;
|
||||
|
||||
/**
|
||||
* 继承Sa-Token行为Bean默认实现, 重写部分逻辑
|
||||
*/
|
||||
//@Component
|
||||
public class MySaTokenAction extends SaTokenActionDefaultImpl {
|
||||
|
||||
/**
|
||||
* 重写Sa-Token的注解处理器,加强注解合并功能
|
||||
* @param target see note
|
||||
*/
|
||||
@Override
|
||||
protected void validateAnnotation(AnnotatedElement target) {
|
||||
|
||||
// 校验 @SaCheckLogin 注解
|
||||
if(AnnotatedElementUtils.isAnnotated(target, SaCheckLogin.class)) {
|
||||
SaCheckLogin at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckLogin.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckRole 注解
|
||||
if(AnnotatedElementUtils.isAnnotated(target, SaCheckRole.class)) {
|
||||
SaCheckRole at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckRole.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckPermission 注解
|
||||
if(AnnotatedElementUtils.isAnnotated(target, SaCheckPermission.class)) {
|
||||
SaCheckPermission at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckPermission.class);
|
||||
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
|
||||
}
|
||||
|
||||
// 校验 @SaCheckSafe 注解
|
||||
if(AnnotatedElementUtils.isAnnotated(target, SaCheckSafe.class)) {
|
||||
SaCheckSafe at = AnnotatedElementUtils.getMergedAnnotation(target, SaCheckSafe.class);
|
||||
SaManager.getStpLogic(null).checkByAnnotation(at);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,14 +4,16 @@ import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token 权限验证工具类 (User版)
|
||||
* Sa-Token 权限认证工具类
|
||||
* @author kong
|
||||
*/
|
||||
@Component
|
||||
@@ -35,6 +37,16 @@ public class StpUserUtil {
|
||||
return stpLogic.getLoginType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 StpLogic 对象
|
||||
* @param stpLogic /
|
||||
*/
|
||||
public static void setStpLogic(StpLogic stpLogic) {
|
||||
StpUtil.stpLogic = stpLogic;
|
||||
// 防止自定义 stpLogic 被覆盖
|
||||
SaManager.putStpLogic(stpLogic);
|
||||
}
|
||||
|
||||
|
||||
// =================== 获取token 相关 ===================
|
||||
|
||||
@@ -74,6 +86,8 @@ public class StpUserUtil {
|
||||
|
||||
// =================== 登录相关操作 ===================
|
||||
|
||||
// --- 登录
|
||||
|
||||
/**
|
||||
* 会话登录
|
||||
* @param id 账号id,建议的类型:(long | int | String)
|
||||
@@ -108,16 +122,37 @@ public class StpUserUtil {
|
||||
public static void login(Object id, SaLoginModel loginModel) {
|
||||
stpLogic.login(id, loginModel);
|
||||
}
|
||||
|
||||
// --- 注销
|
||||
|
||||
/**
|
||||
* 会话注销
|
||||
* 会话注销
|
||||
*/
|
||||
public static void logout() {
|
||||
stpLogic.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定Token
|
||||
* 会话注销,根据账号id
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logout(Object loginId) {
|
||||
stpLogic.logout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id 和 设备标识
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表所有注销设备)
|
||||
*/
|
||||
public static void logout(Object loginId, String device) {
|
||||
stpLogic.logout(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据指定 Token
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
*/
|
||||
public static void logoutByTokenValue(String tokenValue) {
|
||||
@@ -125,24 +160,48 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
* 踢人下线,根据账号id
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId) {
|
||||
stpLogic.logoutByLoginId(loginId);
|
||||
public static void kickout(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢人下线,根据账号id 和 设备标识
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表踢出所有设备)
|
||||
*/
|
||||
public static void kickout(Object loginId, String device) {
|
||||
stpLogic.kickout(loginId, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销,根据账号id & 设备标识 (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识
|
||||
* 踢人下线,根据指定 Token
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
|
||||
*
|
||||
* @param tokenValue 指定token
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId, String device) {
|
||||
stpLogic.logoutByLoginId(loginId, device);
|
||||
public static void kickoutByTokenValue(String tokenValue) {
|
||||
stpLogic.kickoutByTokenValue(tokenValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 顶人下线,根据账号id 和 设备标识
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
|
||||
*
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表顶替所有设备)
|
||||
*/
|
||||
public static void replaced(Object loginId, String device) {
|
||||
stpLogic.replaced(loginId, device);
|
||||
}
|
||||
|
||||
|
||||
// 查询相关
|
||||
|
||||
/**
|
||||
@@ -220,7 +279,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
|
||||
// =================== session相关 ===================
|
||||
// =================== User-Session 相关 ===================
|
||||
|
||||
/**
|
||||
* 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
|
||||
@@ -268,7 +327,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
|
||||
// =================== token专属session ===================
|
||||
// =================== Token-Session 相关 ===================
|
||||
|
||||
/**
|
||||
* 获取指定Token-Session,如果Session尚未创建,则新建并返回
|
||||
@@ -310,7 +369,7 @@ public class StpUserUtil {
|
||||
// =================== 过期时间相关 ===================
|
||||
|
||||
/**
|
||||
* 获取当前登录者的token剩余有效时间 (单位: 秒)
|
||||
* 获取当前登录者的 token 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getTokenTimeout() {
|
||||
@@ -318,7 +377,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录者的Session剩余有效时间 (单位: 秒)
|
||||
* 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getSessionTimeout() {
|
||||
@@ -326,7 +385,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前token的专属Session剩余有效时间 (单位: 秒)
|
||||
* 获取当前 Token-Session 剩余有效时间 (单位: 秒)
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public static long getTokenSessionTimeout() {
|
||||
@@ -334,8 +393,8 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前token[临时过期]剩余有效时间 (单位: 秒)
|
||||
* @return token[临时过期]剩余有效时间
|
||||
* 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
|
||||
* @return token [临时过期] 剩余有效时间
|
||||
*/
|
||||
public static long getTokenActivityTimeout() {
|
||||
return stpLogic.getTokenActivityTimeout();
|
||||
@@ -346,7 +405,7 @@ public class StpUserUtil {
|
||||
// =================== 角色验证操作 ===================
|
||||
|
||||
/**
|
||||
* 指定账号id是否含有角色标识, 返回true或false
|
||||
* 判断:指定账号id是否含有角色标识, 返回true或false
|
||||
* @param loginId 账号id
|
||||
* @param role 角色标识
|
||||
* @return 是否含有指定角色标识
|
||||
@@ -356,16 +415,34 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识, 返回true或false
|
||||
* 判断:当前账号是否含有指定角色标识, 返回true或false
|
||||
* @param role 角色标识
|
||||
* @return 是否含有指定角色标识
|
||||
*/
|
||||
public static boolean hasRole(String role) {
|
||||
return stpLogic.hasRole(role);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
|
||||
* 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* @param roleArray 角色标识数组
|
||||
* @return true或false
|
||||
*/
|
||||
public static boolean hasRoleAnd(String... roleArray){
|
||||
return stpLogic.hasRoleAnd(roleArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* @param roleArray 角色标识数组
|
||||
* @return true或false
|
||||
*/
|
||||
public static boolean hasRoleOr(String... roleArray){
|
||||
return stpLogic.hasRoleOr(roleArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
|
||||
* @param role 角色标识
|
||||
*/
|
||||
public static void checkRole(String role) {
|
||||
@@ -373,7 +450,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
|
||||
* @param roleArray 角色标识数组
|
||||
*/
|
||||
public static void checkRoleAnd(String... roleArray){
|
||||
@@ -381,18 +458,27 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
|
||||
* @param roleArray 角色标识数组
|
||||
*/
|
||||
public static void checkRoleOr(String... roleArray){
|
||||
stpLogic.checkRoleOr(roleArray);
|
||||
}
|
||||
|
||||
|
||||
// --
|
||||
/**
|
||||
* 返回当前账号所拥有的角色标识集合
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getRoleList() {
|
||||
return stpLogic.getRoleList();
|
||||
}
|
||||
|
||||
|
||||
// =================== 权限验证操作 ===================
|
||||
|
||||
/**
|
||||
* 指定账号id是否含有指定权限, 返回true或false
|
||||
* 判断:指定账号id是否含有指定权限, 返回true或false
|
||||
* @param loginId 账号id
|
||||
* @param permission 权限码
|
||||
* @return 是否含有指定权限
|
||||
@@ -402,7 +488,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限, 返回true或false
|
||||
* 判断:当前账号是否含有指定权限, 返回true或false
|
||||
* @param permission 权限码
|
||||
* @return 是否含有指定权限
|
||||
*/
|
||||
@@ -411,7 +497,25 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||
* 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
|
||||
* @param permissionArray 权限码数组
|
||||
* @return true 或 false
|
||||
*/
|
||||
public static boolean hasPermissionAnd(String... permissionArray){
|
||||
return stpLogic.hasPermissionAnd(permissionArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* @param permissionArray 权限码数组
|
||||
* @return true 或 false
|
||||
*/
|
||||
public static boolean hasPermissionOr(String... permissionArray){
|
||||
return stpLogic.hasPermissionOr(permissionArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
|
||||
* @param permission 权限码
|
||||
*/
|
||||
public static void checkPermission(String permission) {
|
||||
@@ -419,7 +523,7 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
|
||||
* 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
|
||||
* @param permissionArray 权限码数组
|
||||
*/
|
||||
public static void checkPermissionAnd(String... permissionArray) {
|
||||
@@ -427,13 +531,22 @@ public class StpUserUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
|
||||
* @param permissionArray 权限码数组
|
||||
*/
|
||||
public static void checkPermissionOr(String... permissionArray) {
|
||||
stpLogic.checkPermissionOr(permissionArray);
|
||||
}
|
||||
|
||||
// --
|
||||
/**
|
||||
* 返回当前账号所拥有的权限码集合
|
||||
* @return /
|
||||
*/
|
||||
public static List<String> getPermissionList() {
|
||||
return stpLogic.getPermissionList();
|
||||
}
|
||||
|
||||
|
||||
// =================== id 反查token 相关操作 ===================
|
||||
|
||||
@@ -602,7 +715,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* 在当前会话 开启二级认证
|
||||
* @param timeout 维持时间 (单位: 秒)
|
||||
* @param safeTime 维持时间 (单位: 秒)
|
||||
*/
|
||||
public static void openSafe(long safeTime) {
|
||||
stpLogic.openSafe(safeTime);
|
||||
@@ -625,7 +738,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
|
||||
* @return
|
||||
* @return 剩余有效时间
|
||||
*/
|
||||
public static long getSafeTime() {
|
||||
return stpLogic.getSafeTime();
|
||||
@@ -643,6 +756,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 获取当前StpLogin的loginKey
|
||||
* @return 当前StpLogin的loginKey
|
||||
*/
|
||||
@@ -653,6 +767,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
*/
|
||||
@@ -663,6 +778,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param device 设备标识
|
||||
@@ -674,6 +790,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param isLastingCookie 是否为持久Cookie
|
||||
@@ -685,6 +802,7 @@ public class StpUserUtil {
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 在当前会话上登录id, 并指定所有登录参数Model
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param loginModel 此次登录的参数Model
|
||||
@@ -694,4 +812,27 @@ public class StpUserUtil {
|
||||
stpLogic.login(loginId, loginModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId) {
|
||||
stpLogic.kickout(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id and 设备标识 (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识 (填null代表所有注销设备)
|
||||
*/
|
||||
public static void logoutByLoginId(Object loginId, String device) {
|
||||
stpLogic.kickout(loginId, device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckBasic;
|
||||
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.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 注解鉴权测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/at/")
|
||||
public class AtController {
|
||||
|
||||
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
|
||||
@SaCheckLogin
|
||||
@RequestMapping("checkLogin")
|
||||
public SaResult checkLogin() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
|
||||
@SaCheckPermission("user-add")
|
||||
@RequestMapping("checkPermission")
|
||||
public SaResult checkPermission() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
|
||||
@SaCheckPermission({"user-add", "user-delete", "user-update"})
|
||||
@RequestMapping("checkPermissionAnd")
|
||||
public SaResult checkPermissionAnd() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
|
||||
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
|
||||
@RequestMapping("checkPermissionOr")
|
||||
public SaResult checkPermissionOr() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
|
||||
@SaCheckRole("admin")
|
||||
@RequestMapping("checkRole")
|
||||
public SaResult checkRole() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 完成二级认证 ---- http://localhost:8081/at/openSafe
|
||||
@RequestMapping("openSafe")
|
||||
public SaResult openSafe() {
|
||||
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
|
||||
@SaCheckSafe
|
||||
@RequestMapping("checkSafe")
|
||||
public SaResult checkSafe() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
|
||||
@SaCheckBasic(account = "sa:123456")
|
||||
@RequestMapping("checkBasic")
|
||||
public SaResult checkBasic() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableLoginException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
|
||||
public class GlobalException {
|
||||
|
||||
// 在当前类每个方法进入之前触发的操作
|
||||
@ModelAttribute
|
||||
public void get(HttpServletRequest request) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ResponseBody
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
e.printStackTrace();
|
||||
|
||||
// 不同异常返回不同状态码
|
||||
AjaxJson aj = null;
|
||||
if (e instanceof NotLoginException) { // 如果是未登录异常
|
||||
NotLoginException ee = (NotLoginException) e;
|
||||
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
|
||||
} else if(e instanceof NotRoleException) { // 如果是角色异常
|
||||
NotRoleException ee = (NotRoleException) e;
|
||||
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
|
||||
} else if(e instanceof NotPermissionException) { // 如果是权限异常
|
||||
NotPermissionException ee = (NotPermissionException) e;
|
||||
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
|
||||
} else if(e instanceof DisableLoginException) { // 如果是被封禁异常
|
||||
DisableLoginException ee = (DisableLoginException) e;
|
||||
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
|
||||
} else { // 普通异常, 输出:500 + 异常信息
|
||||
aj = AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
|
||||
// 输出到客户端
|
||||
// response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象
|
||||
// response.getWriter().print(new ObjectMapper().writeValueAsString(aj));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 全局异常拦截(拦截项目中的NotLoginException异常)
|
||||
// @ExceptionHandler(NotLoginException.class)
|
||||
// public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response)
|
||||
// throws Exception {
|
||||
//
|
||||
// // 打印堆栈,以供调试
|
||||
// nle.printStackTrace();
|
||||
//
|
||||
// // 判断场景值,定制化异常信息
|
||||
// String message = "";
|
||||
// if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
|
||||
// message = "未提供token";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
|
||||
// message = "token无效";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
|
||||
// message = "token已过期";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
|
||||
// message = "token已被顶下线";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
|
||||
// message = "token已被踢下线";
|
||||
// }
|
||||
// else {
|
||||
// message = "当前会话未登录";
|
||||
// }
|
||||
//
|
||||
// // 返回给前端
|
||||
// return AjaxJson.getError(message);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.ok("是否登录:" + StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
|
||||
@RequestMapping("tokenInfo")
|
||||
public SaResult tokenInfo() {
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
}
|
||||
|
||||
// 测试注销 ---- http://localhost:8081/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
|
||||
// 测试登录接口, 浏览器访问: http://localhost:8081/test/login
|
||||
@RequestMapping("login")
|
||||
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
|
||||
@@ -187,7 +186,7 @@ public class TestController {
|
||||
// 先登录上
|
||||
StpUtil.login(10001);
|
||||
// 踢下线
|
||||
StpUtil.logoutByLoginId(10001);
|
||||
StpUtil.kickout(10001);
|
||||
// 再尝试获取
|
||||
StpUtil.getLoginId();
|
||||
// 返回
|
||||
@@ -240,8 +239,8 @@ public class TestController {
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public AjaxJson test() {
|
||||
System.out.println("进来了");
|
||||
return AjaxJson.getSuccess();
|
||||
System.out.println("------------进来了");
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test2
|
||||
@@ -249,6 +248,5 @@ public class TestController {
|
||||
public AjaxJson test2() {
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user/")
|
||||
public class UserController {
|
||||
|
||||
// 测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
|
||||
@RequestMapping("doLogin")
|
||||
public String doLogin(String username, String password) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(username) && "123456".equals(password)) {
|
||||
StpUtil.login(10001);
|
||||
return "登录成功";
|
||||
}
|
||||
return "登录失败";
|
||||
}
|
||||
|
||||
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public String isLogin(String username, String password) {
|
||||
return "当前会话是否登录:" + StpUtil.isLogin();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ sa-token:
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: false
|
||||
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
@@ -30,8 +30,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</h2>
|
||||
<p>当前是否登录:<b class="is-login"></b></p>
|
||||
<p>
|
||||
<a href="javascript:location.href='sso-login.html?back=' + encodeURIComponent(location.href);">登录</a>
|
||||
<a href="javascript:location.href=baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);">注销</a>
|
||||
</p>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 后端接口地址
|
||||
var baseUrl = "http://sa-sso-client1.com:9001";
|
||||
|
||||
// 查询当前会话是否登录
|
||||
$.ajax({
|
||||
url: baseUrl + '/isLogin',
|
||||
type: "post",
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"satoken": localStorage.getItem("satoken")
|
||||
},
|
||||
success: function(res){
|
||||
$('.is-login').html(res.data + '');
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-登录页</title>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-box">
|
||||
|
||||
</div>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 后端接口地址
|
||||
var baseUrl = "http://sa-sso-client1.com:9001";
|
||||
|
||||
var back = getParam('back', '/');
|
||||
var ticket = getParam('ticket');
|
||||
$(function() {
|
||||
if(ticket) {
|
||||
doLoginByTicket(ticket);
|
||||
} else {
|
||||
goSsoAuthUrl();
|
||||
}
|
||||
})
|
||||
|
||||
// 重定向至认证中心
|
||||
function goSsoAuthUrl() {
|
||||
sa.ajax('/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
|
||||
console.log(res);
|
||||
location.href = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
// 根据ticket值登录
|
||||
function doLoginByTicket(ticket) {
|
||||
sa.ajax('/doLoginByTicket', {ticket: ticket}, function(res) {
|
||||
console.log(res);
|
||||
if(res.code == 200) {
|
||||
localStorage.setItem('satoken', res.data);
|
||||
location.href = decodeURIComponent(back);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 从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>
|
||||
<script type="text/javascript">
|
||||
var sa = {};
|
||||
|
||||
// 封装一下Ajax
|
||||
sa.ajax = function(url, data, successFn) {
|
||||
// sa.loading("正在努力加载...");
|
||||
$.ajax({
|
||||
url: baseUrl + url,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
satoken: localStorage.getItem('satoken')
|
||||
},
|
||||
success: function(res){
|
||||
console.log('返回数据:', res);
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
if(xhr.status == 0){
|
||||
return alert('无法连接到服务器,请检查网络');
|
||||
}
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,25 +5,7 @@ body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
|
||||
/* 视图盒子 */
|
||||
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
|
||||
/* 背景 EAEFF3 */
|
||||
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
|
||||
.bg-2{height: 50%; background-color: #EAEFF3;}
|
||||
|
||||
/* 渐变背景 */
|
||||
/*.bg-1{
|
||||
background-size: 500%;
|
||||
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
|
||||
animation: bganimation 30s infinite;
|
||||
}
|
||||
@keyframes bganimation{
|
||||
0%{background-position: 0% 50%;}
|
||||
50%{background-position: 100% 50%;}
|
||||
100%{background-position: 0% 50%;}
|
||||
} */
|
||||
/* 背景 */
|
||||
.bg-1{background: #101C34;}
|
||||
.bg-2{background: #101C34;}
|
||||
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
|
||||
|
||||
.bg-1{height: 100%; background: #c0cCf4;}
|
||||
|
||||
/* 内容盒子 */
|
||||
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 服务端地址
|
||||
var baseUrl = "http://sa-sso-server.com:9000";
|
||||
|
||||
// sa
|
||||
var sa = {};
|
||||
|
||||
// 打开loading
|
||||
sa.loading = function(msg) {
|
||||
layer.closeAll(); // 开始前先把所有弹窗关了
|
||||
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load'});
|
||||
};
|
||||
|
||||
// 隐藏loading
|
||||
sa.hideLoading = function() {
|
||||
layer.closeAll();
|
||||
};
|
||||
|
||||
// 封装一下Ajax
|
||||
sa.ajax = function(url, data, successFn) {
|
||||
$.ajax({
|
||||
url: baseUrl + url,
|
||||
type: "post",
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'satoken': localStorage.getItem('satoken')
|
||||
},
|
||||
success: function(res){
|
||||
console.log('返回数据:', res);
|
||||
successFn(res);
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
if(xhr.status == 0){
|
||||
return alert('无法连接到服务器,请检查网络');
|
||||
}
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------- 相关事件 -----------------------------------
|
||||
|
||||
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
|
||||
sa.ajax("/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
|
||||
if(res.code == 200) {
|
||||
// 已登录,并且redirect地址有效,开始跳转
|
||||
location.href = decodeURIComponent(res.data);
|
||||
} else if(res.code == 401) {
|
||||
console.log('未登录');
|
||||
} else {
|
||||
layer.alert(res.msg);
|
||||
}
|
||||
})
|
||||
|
||||
// 登录
|
||||
$('.login-btn').click(function(){
|
||||
sa.loading("正在登录...");
|
||||
// 开始登录
|
||||
var data = {
|
||||
name: $('[name=name]').val(),
|
||||
pwd: $('[name=pwd]').val()
|
||||
};
|
||||
sa.ajax("/sso/doLogin", data, function(res) {
|
||||
sa.hideLoading();
|
||||
if(res.code == 200) {
|
||||
localStorage.setItem('satoken', res.data);
|
||||
layer.msg('登录成功', {anim: 0, icon: 6 });
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 800);
|
||||
} else {
|
||||
layer.msg(res.msg, {anim: 6, icon: 2 });
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// 绑定回车事件
|
||||
$('[name=name],[name=pwd]').bind('keypress', function(event){
|
||||
if(event.keyCode == "13") {
|
||||
$('.login-btn').click();
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框获取焦点
|
||||
$("[name=name]").focus();
|
||||
|
||||
// 从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] + (pair[2] ? '=' + pair[2] : '');}
|
||||
}
|
||||
return(defaultValue == undefined ? null : defaultValue);
|
||||
}
|
||||
|
||||
// 打印信息
|
||||
var str = "This page is provided by Sa-Token, Please refer to: " + "http://sa-token.dev33.cn/";
|
||||
console.log(str);
|
||||
@@ -5,16 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<base th:href="@{/}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="stylesheet" href="./sa-res/login.css">
|
||||
<link rel="stylesheet" href="./login.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="view-box">
|
||||
<div class="bg-1"></div>
|
||||
<div class="bg-2"></div>
|
||||
<div class="content-box">
|
||||
<div class="login-box">
|
||||
<div class="from-box">
|
||||
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
|
||||
<h2 class="from-title">Sa-SSO-Server 认证中心(前后端分离版)</h2>
|
||||
<div class="from-item">
|
||||
<input class="s-input" name="name" placeholder="请输入账号" />
|
||||
</div>
|
||||
@@ -37,9 +36,9 @@
|
||||
</div>
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="./sa-res/jquery.min.js"></script>
|
||||
<script src="./sa-res/layer/layer.js"></script>
|
||||
<script src="./sa-res/login.js"></script>
|
||||
<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 src="./login.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,7 @@
|
||||
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-sso3-server</artifactId>
|
||||
<artifactId>sa-token-demo-sso-server</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.23.0</sa-token-version>
|
||||
<sa-token-version>1.27.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<!-- SpringBoot Web依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
@@ -40,26 +40,24 @@
|
||||
<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>
|
||||
|
||||
<!-- 视图引擎 -->
|
||||
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Http请求工具 -->
|
||||
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
|
||||
<dependency>
|
||||
<groupId>com.ejlchina</groupId>
|
||||
<artifactId>okhttps</artifactId>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ public class SaSsoServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaSsoServerApplication.class, args);
|
||||
System.out.println("\nSa-Token-SSO 认证中心启动成功");
|
||||
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
* @author kong
|
||||
*/
|
||||
@Component
|
||||
@Order(-200)
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
static final String OPTIONS = "OPTIONS";
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
// 允许指定域访问跨域资源
|
||||
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||
// 允许所有请求方式
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
||||
// 有效时间
|
||||
response.setHeader("Access-Control-Max-Age", "3600");
|
||||
// 允许的header参数
|
||||
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");
|
||||
|
||||
// 如果是预检请求,直接返回
|
||||
if (OPTIONS.equals(request.getMethod())) {
|
||||
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
|
||||
response.getWriter().print("");
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("*********************************过滤器被使用**************************");
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoConsts;
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码 (SSO-Server端)
|
||||
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class H5Controller {
|
||||
|
||||
/**
|
||||
* 获取 redirectUrl
|
||||
*/
|
||||
@RequestMapping("/getRedirectUrl")
|
||||
private Object getRedirectUrl(String redirect, String mode) {
|
||||
// 未登录情况下,返回 code=401
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return SaResult.code(401);
|
||||
}
|
||||
// 已登录情况下,构建 redirectUrl
|
||||
if(SaSsoConsts.MODE_SIMPLE.equals(mode)) {
|
||||
// 模式一
|
||||
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
|
||||
return SaResult.data(redirect);
|
||||
} else {
|
||||
// 模式二或模式三
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
return SaResult.data(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
public SaResult handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,9 +7,11 @@ import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class ExceptionHandle {
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.sso.SaSsoHandle;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 Controller
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class SsoServerController {
|
||||
|
||||
/*
|
||||
* SSO-Server端:处理所有SSO相关请求
|
||||
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
|
||||
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
|
||||
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
|
||||
* http://{host}:{port}/sso/logout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
|
||||
*/
|
||||
@RequestMapping("/sso/*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.serverRequest();
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
|
||||
// 配置:未登录时返回的View
|
||||
cfg.sso.setNotLoginView(() -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
});
|
||||
|
||||
// 配置:登录处理函数
|
||||
cfg.sso.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
});
|
||||
|
||||
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
|
||||
cfg.sso.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,34 +2,42 @@
|
||||
server:
|
||||
port: 9000
|
||||
|
||||
# Sa-Token配置
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
# SSO-相关配置
|
||||
# -------------- SSO-模式一相关配置 (非模式一不需要配置)
|
||||
# cookie:
|
||||
# 配置Cookie作用域
|
||||
# domain: stp.com
|
||||
|
||||
# ------- SSO-模式二相关配置
|
||||
sso:
|
||||
# Ticket有效期 (单位: 秒),默认五分钟
|
||||
ticket-timeout: 300
|
||||
# 所有允许的授权回调地址
|
||||
allow-url: http://sa-sso-client1.com:9001/sso/login, http://sa-sso-client2.com:9001/sso/login, http://sa-sso-client3.com:9001/sso/login
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# 打开单点注销功能
|
||||
# 所有允许的授权回调地址
|
||||
allow-url: "*"
|
||||
# 是否打开单点注销功能
|
||||
is-slo: true
|
||||
|
||||
# ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开) -------
|
||||
# 是否打开模式三
|
||||
isHttp: true
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
|
||||
|
||||
spring:
|
||||
# Redis配置
|
||||
# Redis配置 (SSO模式一和模式二使用Redis来同步会话)
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 5
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |