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

Compare commits

...

111 Commits

Author SHA1 Message Date
click33 f8afd89154 v1.39.0 update 2024-08-28 09:58:06 +08:00
click33 e1141ef942 优化文档SSO集成流程 2024-08-28 02:05:02 +08:00
click33 3eaa6b9baf 修复前端提交同名cookie时的框架错读现象 2024-08-28 00:28:41 +08:00
click33 acd1bcb518 Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2024-08-27 10:08:59 +08:00
click33 4683c3f878 Merge pull request #656 from fooooxxxx/fix/timeout-integer-overflow
修复 SaLoginModel 的 timeout 超出整型范围时返回负数导致 Cookie 被设置为 会话期Cookie 的问题
2024-08-27 10:07:46 +08:00
click33 0229efa5fd 优化示例 2024-08-26 23:14:59 +08:00
click33 b1180a219d 新增基于内存形式的 client 信息配置 2024-08-26 18:50:38 +08:00
click33 b552dd334c 优化 sa-token-oauth2 异常细分状态码 2024-08-26 02:34:34 +08:00
click33 ffd557c5d7 优化日志打印 2024-08-25 21:25:10 +08:00
click33 60b7c9036f 重构改名 PastToken -> LowerClientToken 2024-08-25 20:00:05 +08:00
click33 beb958f274 新增sa-token-oauth2 注解鉴权 2024-08-25 14:02:50 +08:00
click33 6a9f25093d 细节优化 2024-08-25 11:46:43 +08:00
click33 672f3bd081 错误调用API修复 2024-08-25 10:50:16 +08:00
click33 7f20c8d450 完善 SaOAuth2Util 相关方法 2024-08-25 10:32:52 +08:00
click33 e0a609b128 增加 state 值校验 2024-08-24 20:18:23 +08:00
click33 d3b337a6a6 新增 revokeClientToken、revokeClientTokenByIndex: Client-Token 回收函数 2024-08-24 18:55:48 +08:00
click33 a1560ce0a7 新增 StpLogic#getOrCreateLoginSession 方法,获取指定账号 id 的登录会话数据,如果获取不到则创建并返回 2024-08-24 17:55:46 +08:00
click33 8235fe7633 新增 oauth2 模块常见问答页 2024-08-24 17:02:23 +08:00
click33 8c008a1aec 新增 hideStatusField 配置项,用于指定是否在返回值中隐藏默认的状态字段 (code、msg、data) 2024-08-24 04:44:14 +08:00
click33 760805f78c 新增 mode4ReturnAccessToken 配置,指定模式4是否返回 AccessToken 字段 2024-08-24 04:27:29 +08:00
click33 06b06cdb5e TokenType 指定方式细节重构 2024-08-24 04:14:12 +08:00
click33 2d13e908b1 access_token 读取兼容 Bearer Token 方式 2024-08-24 03:46:03 +08:00
click33 a7a3e8c14f 新增 OIDC 协议实现 2024-08-24 00:20:17 +08:00
click33 419ca3797c 细节优化、文档优化 2024-08-23 16:18:24 +08:00
click33 3345e3aaf9 sa-token-oauth2 新增自定义 grant_type 能力 2024-08-23 03:24:30 +08:00
click33 4aa4941598 新增 scope 等级划分,可指定哪些权限需要强制每次手动授权 2024-08-21 13:57:05 +08:00
click33 1bc59dc14c 新增 _FINALLY_WORK_SCOPE 最终权限处理器 2024-08-20 17:34:18 +08:00
click33 0ca8a1ab2d 注解处理器父接口重命名 SaAnnotationAbstractHandler -> SaAnnotationHandlerInterface 2024-08-20 13:00:22 +08:00
click33 c4b6a6381e 完善 OAuth2-自定义权限处理器 章节文档 2024-08-20 12:58:56 +08:00
click33 174a94db01 完整适配拆分式路由写法 2024-08-19 23:29:05 +08:00
click33 25b24414ff 初步完善 oauth2 相关文档 2024-08-19 17:49:59 +08:00
click33 e8d3a6f137 部分配置项改为true 2024-08-18 22:30:39 +08:00
click33 dd359b20d6 新增 addAllowUrls addContractScopes 方法,简化 SaClientModel 构建代码 2024-08-18 21:11:34 +08:00
click33 c17b244452 提供默认 openid 生成算法 2024-08-18 20:58:09 +08:00
click33 f0c2949539 优化自定义 scope 时的扩展参数处理逻辑 2024-08-18 20:22:20 +08:00
click33 c03bccd956 新增 ClientToken 与自定义权限的对接 2024-08-18 19:06:36 +08:00
click33 1ee1340192 token创建抽离到全局策略 2024-08-18 18:41:22 +08:00
click33 281985bfdb 新增自定义 scope 处理器支持 2024-08-18 18:08:46 +08:00
click33 2b46d27b87 抽取数据构建器接口 2024-08-18 09:53:44 +08:00
click33 b4c06f2265 抽离 SaOAuth2Dao 接口,负责数据持久 2024-08-17 18:29:54 +08:00
click33 d9f9238591 Merge pull request #671 from zhongjun96/dev_update_doc
isShare配置增加说明
2024-08-17 16:15:24 +08:00
zhongjun a55396087c Update config.md 2024-08-17 15:26:26 +08:00
click33 0b3781d386 常见错误新增QA 2024-08-17 13:03:01 +08:00
click33 a2289dc6a1 修复不准确的注释信息 2024-08-16 23:25:27 +08:00
click33 c485eba134 sa-token-oauth2 redirect_url 参数校验增加规则:不允许出现@字符、*通配符只能出现在最后一位 2024-08-16 21:07:13 +08:00
click33 7f745a7ba4 兼容从 basic auth 请求头中读取 client 信息 2024-08-16 18:50:10 +08:00
click33 12c645aaed 新增 Chat2DB 友联 2024-08-15 19:49:10 +08:00
click33 15e2d9f668 将 scope 字段改为 List 类型 2024-08-14 18:11:20 +08:00
click33 47cf8939cb 更改 sa-token-oauth2 实体类相关包位置 2024-08-14 13:27:35 +08:00
click33 890da5d934 更换Q群链接 2024-08-14 11:33:54 +08:00
goodsWox 61c1ed221b squash! 修复 SaLoginModel 的 timeout 超出整型范围时返回负数的问题 2024-08-14 09:46:06 +08:00
grasse f37c0770bf Merge branch 'dromara:dev' into fix/timeout-integer-overflow 2024-08-14 09:21:37 +08:00
click33 ae87b11d43 新增 SaOAuth2DataResolver 数据解析器,负责 Web 交互层面的数据进出 2024-08-13 15:04:39 +08:00
click33 b1e2e8a526 sa-token-oauth2 适配多账号模式,允许重写使用的会话 StpLogic 2024-08-12 07:56:29 +08:00
click33 6c0d856cb8 sa-token-oauth2 适配拆分式路由 2024-08-12 07:36:43 +08:00
click33 eeed140424 重构 sa-token-oauth2:提取数据加载器 Bean,提供更方便的自定义数据加载方案。 2024-08-12 07:05:43 +08:00
click33 101a577ded 新增 JWT 功能代码对比 2024-08-11 06:22:11 +08:00
click33 2d5478ef7f 文档与新增 SpringSecurity 功能代码实现对比 2024-08-11 04:09:21 +08:00
click33 036e10ef79 文档新增 Sa-Token 与 Shiro 功能代码实现对比 2024-08-08 06:52:24 +08:00
click33 aefcdf12d4 优化文档 [将权限数据放在缓存里] 示例 2024-08-05 15:50:53 +08:00
click33 f926a3f136 sa-token-solon-plugin 适配自定义注解扩展 2024-08-04 01:42:30 +08:00
click33 16b1e4f99c jfinal and jboot plugin update 2024-08-04 01:41:17 +08:00
click33 aa2e9a5c50 使 sa-token-spring-aop 插件支持自定义注解鉴权 2024-08-04 01:11:40 +08:00
click33 c38eb0c68c 注解处理器传递参数改为 Method 2024-08-03 19:22:21 +08:00
click33 cd0c20793a SaAnnotationAbstractHandler 由抽象类改为接口 2024-08-03 19:08:51 +08:00
click33 834e1d5b34 重构注解鉴权底层,可以方便的自定义注解了 2024-08-03 18:50:11 +08:00
click33 a3746878de 优化 sso-server 前后端分离时的跳转流程 2024-08-02 03:17:09 +08:00
click33 dab9e15673 优化 sso-server 前后端分离 demo 代码 2024-08-02 02:48:10 +08:00
click33 ebe405fe8b 优化 OAuth2 模块 checkClientToken 方法异常提示信息 2024-08-02 02:19:27 +08:00
click33 1298012bcb 修复 sa-token-quick-login 插件无法正常拦截的问题 2024-08-02 02:13:53 +08:00
click33 ba79154dc8 不同 client 不同登录页 2024-08-01 01:59:57 +08:00
click33 249c584a9f Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2024-08-01 01:00:00 +08:00
click33 004babd8bb 完善 sso 模式三文档 2024-08-01 00:58:48 +08:00
click33 968c5ea1b6 SSO 认证中心增加平台中心跳转模式 demo 2024-07-31 23:54:46 +08:00
click33 bb8a206dea 文档新增数据结构说明 2024-07-31 17:40:48 +08:00
click33 93a03c92e3 修复部分场景下文档右侧目录高度不能正确读取的问题 2024-07-30 15:29:05 +08:00
click33 d62cdc79e0 优化文档 2024-07-30 12:48:27 +08:00
click33 80e4c1aaa7 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2024-07-29 22:44:00 +08:00
click33 ae67ad6a02 优化文档结构样式 2024-07-29 00:43:55 +08:00
click33 3e7d9636a6 Merge pull request #606 from CuiGeekYoung/dev
兼容请求/oauth2/token接口时Basic中携带clientId和clientSecret的场景
2024-07-23 07:54:08 +08:00
click33 1224397981 更新赞助者名单 2024-07-20 12:19:53 +08:00
goodsWox 637ca491ea 修复 SaLoginModel 的 timeout 超出整型范围时返回负数的问题 2024-07-12 16:22:08 +08:00
巴掌大叔 428016c072 !308 处理解析 JWT 时的 JSONException
* 处理解析 JWT 时的 JSONException
2024-06-25 12:32:02 +00:00
click33 12642cb8fc 更新赞助列表信息 2024-06-21 22:41:22 +08:00
click33 591530be86 补全 dromara 项目列表 2024-06-21 22:33:00 +08:00
click33 f8585ae001 更新赞助者列表 2024-06-21 22:12:00 +08:00
click33 a113560019 代码托管地址展示 2024-06-21 20:47:26 +08:00
click33 2c68248f7f test submit 2024-06-21 20:33:39 +08:00
click33 1550be6436 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2024-06-21 20:32:33 +08:00
click33 1b5022cb4a test submit 2024-06-21 20:30:13 +08:00
黄先生 a5d103e73b 更新demo-ssm里面的注解@SaCheckHttpBasic 2024-06-19 15:27:27 +08:00
黄先生 f9114ab749 版本更新后@SaCheckBasic替换为@SaCheckHttpBasic 2024-06-19 15:22:51 +08:00
click33 a4177c3c7c 删除不必要的注释 2024-06-12 17:48:31 +08:00
刘潇 c1c354b622 !306 【文档】为 web-flux 集成增加了 Kotlin 示例
Merge pull request !306 from 林钟一六/dev
2024-06-12 09:47:13 +00:00
click33 ec40d9cda6 更换友联logo 2024-06-11 17:14:18 +08:00
click33 9540df9e01 更新赞助者名单 2024-06-11 13:57:01 +08:00
click33 a1a0e9ca5a Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2024-06-11 06:27:07 +08:00
click33 dd61ddb0c4 更新赞助者列表 2024-06-11 06:26:48 +08:00
刘潇 b27fff3e02 !307 update sa-token-doc/sso/sso-check-domain.md.
Merge pull request !307 from Jelex/N/A
2024-06-07 09:37:08 +00:00
Jelex 4c59ace69a update sa-token-doc/sso/sso-check-domain.md.
Signed-off-by: Jelex <fanguyinheng@foxmail.com>
2024-06-07 09:36:00 +00:00
林钟一六 f082880568 doc: 增加了 Kotlin 示例 2024-06-06 01:08:28 +08:00
刘潇 2fc0bca584 !301 调整 sa-token-redisson-jackson2 内的版本依赖
Merge pull request !301 from 西东/dev
2024-05-27 14:09:22 +00:00
noear fc6761e020 调整 sa-token-redisson-jackson2 内的版本依赖 2024-05-27 22:08:10 +08:00
click33 3a81e5f537 修正部分错误描述 2024-05-27 10:40:15 +08:00
click33 131d163fa9 更换加群信息 2024-05-25 11:45:31 +08:00
click33 40b01aba00 修复 前后端分离模式下 集成 sso 的 demo 示例错误代码 2024-05-17 11:41:42 +08:00
click33 a0faf303d8 官网增加视频教程链接 2024-05-16 01:54:37 +08:00
click33 592cd79507 更换qq群链接 2024-05-15 15:05:51 +08:00
click33 2d4db297ac 修复部分错别字 2024-05-13 09:24:22 +08:00
click33 a86706b2c2 更新群聊信息 2024-05-12 17:09:59 +08:00
cuiguiyang 54d29f0fb5 兼容请求/oauth2/token接口时Basic中携带clientId和clientSecret的场景 2024-04-09 15:30:59 +08:00
302 changed files with 15217 additions and 4392 deletions
+16 -3
View File
@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.38.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.39.0</h1>
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
@@ -185,14 +185,27 @@ Sa-Token-OAuth2 模块分为四种授权模式,解决不同场景下的授权
- [[ hippo4j ]](https://gitee.com/agentart/hippo4j):强大的动态线程池框架,附带监控报警功能。
- [[ hertzbeat ]](https://gitee.com/dromara/hertzbeat):易用友好的开源实时监控告警系统,无需Agent,高性能集群,强大自定义监控能力。
- [[ Solon ]](https://gitee.com/noear/solon):一个更现代感的应用开发框架:更快、更小、更自由。
- [[ Chat2DB ]](https://github.com/chat2db/Chat2DB):一个AI驱动的数据库管理和BI工具,支持Mysql、pg、Oracle、Redis等22种数据库的管理。
### 代码托管
- Gitee[https://gitee.com/dromara/sa-token](https://gitee.com/dromara/sa-token)
- GitHub[https://github.com/dromara/sa-token](https://github.com/dromara/sa-token)
- GitCode[https://gitcode.com/click33/sa-token](https://gitcode.com/click33/sa-token)
### 交流群
QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)
<!-- QQ交流群:685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)-->
QQ交流群:823181187 [点击加入](https://qm.qq.com/q/EBIJVZBVGE)
微信交流群:
<img src="https://oss.dev33.cn/sa-token/qr/wx-qr-m-400k.png" width="230px" title="微信群" />
<!-- <img src="https://oss.dev33.cn/sa-token/qr/wx-qr-m-400k.png" width="230px" title="微信群" /> -->
<img src="https://oss.dev33.cn/sa-token/qr/i-wx-qr2.png" width="230px" title="微信群" />
(扫码添加微信,备注:sa-token,邀您加入群聊)
+1 -1
View File
@@ -37,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<revision>1.38.0</revision>
<revision>1.39.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
+1 -1
View File
@@ -13,7 +13,7 @@
<url>https://github.com/dromara/sa-token</url>
<properties>
<revision>1.38.0</revision>
<revision>1.39.0</revision>
</properties>
<dependencyManagement>
@@ -39,13 +39,6 @@ public @interface SaCheckOr {
*/
SaCheckLogin[] login() default {};
/**
* 设定 @SaCheckPermission,参考 {@link SaCheckPermission}
*
* @return /
*/
SaCheckPermission[] permission() default {};
/**
* 设定 @SaCheckRole,参考 {@link SaCheckRole}
*
@@ -53,6 +46,13 @@ public @interface SaCheckOr {
*/
SaCheckRole[] role() default {};
/**
* 设定 @SaCheckPermission,参考 {@link SaCheckPermission}
*
* @return /
*/
SaCheckPermission[] permission() default {};
/**
* 设定 @SaCheckSafe,参考 {@link SaCheckSafe}
*
@@ -0,0 +1,52 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 所有注解处理器的父接口
*
* @author click33
* @since 2024/8/2
*/
public interface SaAnnotationHandlerInterface<T extends Annotation> {
/**
* 获取所要处理的注解类型
* @return /
*/
Class<T> getHandlerAnnotationClass();
/**
* 所需要执行的校验方法
* @param at 注解对象
* @param method 被标注的注解的方法引用
*/
@SuppressWarnings("unchecked")
default void check(Annotation at, Method method) {
checkMethod((T) at, method);
}
/**
* 所需要执行的校验方法(转换类型后)
* @param at 注解对象
* @param method 被标注的注解的方法引用
*/
void checkMethod(T at, Method method);
}
@@ -0,0 +1,51 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckDisable;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckDisable 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckDisableHandler implements SaAnnotationHandlerInterface<SaCheckDisable> {
@Override
public Class<SaCheckDisable> getHandlerAnnotationClass() {
return SaCheckDisable.class;
}
@Override
public void checkMethod(SaCheckDisable at, Method method) {
_checkMethod(at.type(), at.value(), at.level());
}
public static void _checkMethod(String type, String[] value, int level) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
Object loginId = stpLogic.getLoginId();
for (String service : value) {
stpLogic.checkDisableLevel(loginId, service, level);
}
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckHttpBasic;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckHttpBasic 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckHttpBasicHandler implements SaAnnotationHandlerInterface<SaCheckHttpBasic> {
@Override
public Class<SaCheckHttpBasic> getHandlerAnnotationClass() {
return SaCheckHttpBasic.class;
}
@Override
public void checkMethod(SaCheckHttpBasic at, Method method) {
_checkMethod(at.realm(), at.account());
}
public static void _checkMethod(String realm, String account) {
SaHttpBasicUtil.check(realm, account);
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckHttpDigest 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckHttpDigestHandler implements SaAnnotationHandlerInterface<SaCheckHttpDigest> {
@Override
public Class<SaCheckHttpDigest> getHandlerAnnotationClass() {
return SaCheckHttpDigest.class;
}
@Override
public void checkMethod(SaCheckHttpDigest at, Method method) {
_checkMethod(at.username(), at.password(), at.realm(), at.value());
}
public static void _checkMethod(String username, String password, String realm, String value) {
// 如果配置了 value,则以 value 优先
if(SaFoxUtil.isNotEmpty(value)){
String[] arr = value.split(":");
if(arr.length != 2){
throw new SaTokenException("注解参数配置错误,格式应如:username:password");
}
SaHttpDigestUtil.check(arr[0], arr[1]);
return;
}
// 如果配置了 username,则分别获取参数
if(SaFoxUtil.isNotEmpty(username)){
SaHttpDigestUtil.check(username, password, realm);
return;
}
// 都没有配置,则根据全局配置参数进行校验
SaHttpDigestUtil.check();
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckLogin 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckLoginHandler implements SaAnnotationHandlerInterface<SaCheckLogin> {
@Override
public Class<SaCheckLogin> getHandlerAnnotationClass() {
return SaCheckLogin.class;
}
@Override
public void checkMethod(SaCheckLogin at, Method method) {
_checkMethod(at.type());
}
public static void _checkMethod(String type) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
stpLogic.checkLogin();
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 注解 SaCheckOr 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckOrHandler implements SaAnnotationHandlerInterface<SaCheckOr> {
@Override
public Class<SaCheckOr> getHandlerAnnotationClass() {
return SaCheckOr.class;
}
@Override
public void checkMethod(SaCheckOr at, Method method) {
_checkMethod(at.login(), at.role(), at.permission(), at.safe(), at.httpBasic(), at.httpDigest(), at.disable(), method);
}
public static void _checkMethod(
SaCheckLogin[] login,
SaCheckRole[] role,
SaCheckPermission[] permission,
SaCheckSafe[] safe,
SaCheckHttpBasic[] httpBasic,
SaCheckHttpDigest[] httpDigest,
SaCheckDisable[] disable,
Method method
) {
// 先把所有注解塞到一个 list 里
List<Annotation> annotationList = new ArrayList<>();
annotationList.addAll(Arrays.asList(login));
annotationList.addAll(Arrays.asList(role));
annotationList.addAll(Arrays.asList(permission));
annotationList.addAll(Arrays.asList(safe));
annotationList.addAll(Arrays.asList(disable));
annotationList.addAll(Arrays.asList(httpBasic));
annotationList.addAll(Arrays.asList(httpDigest));
// 如果 atList 为空,说明 SaCheckOr 上不包含任何注解校验,我们直接跳过即可
if(annotationList.isEmpty()) {
return;
}
// 逐个开始校验 >>>
List<SaTokenException> errorList = new ArrayList<>();
for (Annotation item : annotationList) {
try {
SaAnnotationStrategy.instance.annotationHandlerMap.get(item.annotationType()).check(item, method);
// 只要有一个校验通过,就可以直接返回了
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 执行至此,说明所有注解校验都通过不了,此时 errorList 里面会有多个异常,我们随便抛出一个即可
throw errorList.get(0);
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import java.lang.reflect.Method;
/**
* 注解 SaCheckPermission 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface<SaCheckPermission> {
@Override
public Class<SaCheckPermission> getHandlerAnnotationClass() {
return SaCheckPermission.class;
}
@Override
public void checkMethod(SaCheckPermission at, Method method) {
_checkMethod(at.type(), at.value(), at.mode(), at.orRole());
}
public static void _checkMethod(String type, String[] value, SaMode mode, String[] orRole) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
String[] permissionArray = value;
try {
if(mode == SaMode.AND) {
stpLogic.checkPermissionAnd(permissionArray);
} else {
stpLogic.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : orRole) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (stpLogic.hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckRole 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckRoleHandler implements SaAnnotationHandlerInterface<SaCheckRole> {
@Override
public Class<SaCheckRole> getHandlerAnnotationClass() {
return SaCheckRole.class;
}
@Override
public void checkMethod(SaCheckRole at, Method method) {
_checkMethod(at.type(), at.value(), at.mode());
}
public static void _checkMethod(String type, String[] value, SaMode mode) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
String[] roleArray = value;
if(mode == SaMode.AND) {
stpLogic.checkRoleAnd(roleArray);
} else {
stpLogic.checkRoleOr(roleArray);
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.stp.StpLogic;
import java.lang.reflect.Method;
/**
* 注解 SaCheckSafe 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaCheckSafeHandler implements SaAnnotationHandlerInterface<SaCheckSafe> {
@Override
public Class<SaCheckSafe> getHandlerAnnotationClass() {
return SaCheckSafe.class;
}
@Override
public void checkMethod(SaCheckSafe at, Method method) {
_checkMethod(at.type(), at.value());
}
public static void _checkMethod(String type, String value) {
StpLogic stpLogic = SaManager.getStpLogic(type, false);
stpLogic.checkSafe(value);
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation.handler;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.router.SaRouter;
import java.lang.reflect.Method;
/**
* 注解 SaIgnore 的处理器
*
* @author click33
* @since 2024/8/2
*/
public class SaIgnoreHandler implements SaAnnotationHandlerInterface<SaIgnore> {
@Override
public Class<SaIgnore> getHandlerAnnotationClass() {
return SaIgnore.class;
}
@Override
public void checkMethod(SaIgnore at, Method method) {
_checkMethod();
}
public static void _checkMethod() {
SaRouter.stop();
}
}
@@ -1,99 +0,0 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.basic;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.secure.SaBase64Util;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* <h2> 已更换至包:cn.dev33.satoken.httpauth.basic </h2>
* <h2> 已更换名称:SaHttpBasicTemplate </h2>
*
* Sa-Token Http Basic 认证模块
*
* @author click33
* @since 1.26.0
*/
@Deprecated
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().setCode(SaErrorCode.CODE_10311);
}
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public String getAuthorizationValue() {
// 获取前端提交的请求头 Authorization 参数
String authorization = SaHolder.getRequest().getHeader("Authorization");
// 如果不是以 Basic 作为前缀,则视为无效
if(authorization == null || ! authorization.startsWith("Basic ")) {
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)) {
throwNotBasicAuthException(realm);
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.basic;
/**
* <h2> 已更换至包:cn.dev33.satoken.httpauth.basic </h2>
* <h2> 已更换名称:SaHttpBasicUtil </h2>
*
* Sa-Token Http Basic 认证模块,Util 工具类
*
* @author click33
* @since 1.26.0
*/
@Deprecated
public class SaBasicUtil {
private 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);
}
}
@@ -17,6 +17,7 @@ package cn.dev33.satoken.context.model;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List;
@@ -130,6 +131,20 @@ public interface SaRequest {
*/
String getCookieValue(String name);
/**
* 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的)
* @param name 键
* @return 值
*/
String getCookieFirstValue(String name);
/**
* 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的)
* @param name 键
* @return 值
*/
String getCookieLastValue(String name);
/**
* 返回当前请求path (不包括上下文名称)
* @return /
@@ -156,7 +171,25 @@ public interface SaRequest {
* @return /
*/
String getMethod();
/**
* 返回当前请求 Method 是否为指定值
* @param method method
* @return /
*/
default boolean isMethod(String method) {
return getMethod().equals(method);
}
/**
* 返回当前请求 Method 是否为指定值
* @param method method
* @return /
*/
default boolean isMethod(SaHttpMethod method) {
return getMethod().equals(method.name());
}
/**
* 判断此请求是否为 Ajax 异步请求
* @return /
@@ -21,7 +21,7 @@ package cn.dev33.satoken.exception;
* @author click33
* @since 1.26.0
*/
public class NotBasicAuthException extends SaTokenException {
public class NotHttpBasicAuthException extends SaTokenException {
/**
* 序列化版本号
@@ -34,7 +34,7 @@ public class NotBasicAuthException extends SaTokenException {
/**
* 一个异常代表会话未通过 Http Basic 认证
*/
public NotBasicAuthException() {
public NotHttpBasicAuthException() {
super(BE_MESSAGE);
}
@@ -29,6 +29,6 @@ import java.util.function.BiFunction;
* @since 1.35.0
*/
@FunctionalInterface
public interface SaGetAnnotationFunction extends BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> {
public interface SaGetAnnotationFunction extends BiFunction<AnnotatedElement, Class<? extends Annotation>, Annotation> {
}
@@ -18,7 +18,7 @@ package cn.dev33.satoken.httpauth.basic;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.exception.NotHttpBasicAuthException;
import cn.dev33.satoken.secure.SaBase64Util;
import cn.dev33.satoken.util.SaFoxUtil;
@@ -41,7 +41,7 @@ public class SaHttpBasicTemplate {
*/
public void throwNotBasicAuthException(String realm) {
SaHolder.getResponse().setStatus(401).setHeader("WWW-Authenticate", "Basic Realm=" + realm);
throw new NotBasicAuthException().setCode(SaErrorCode.CODE_10311);
throw new NotHttpBasicAuthException().setCode(SaErrorCode.CODE_10311);
}
/**
@@ -263,11 +263,16 @@ public class SaHttpDigestTemplate {
check(arr[0], arr[1]);
}
// ----------------- 过期方法 -----------------
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckHttpDigest at) {
// 如果配置了 value,则以 value 优先
@@ -90,11 +90,16 @@ public class SaHttpDigestUtil {
saHttpDigestTemplate.check();
}
// ----------------- 过期方法 -----------------
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public static void checkByAnnotation(SaCheckHttpDigest at) {
saHttpDigestTemplate.checkByAnnotation(at);
}
@@ -18,6 +18,7 @@ package cn.dev33.satoken.listener;
import java.util.ArrayList;
import java.util.List;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.SaTokenException;
@@ -287,6 +288,16 @@ public class SaTokenEventCenter {
}
}
/**
* 事件发布:有新的注解处理器载入到框架中
* @param handler 注解处理器
*/
public static void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
for (SaTokenListener listener : listenerList) {
listener.doRegisterAnnotationHandler(handler);
}
}
/**
* 事件发布:有新的 StpLogic 载入到框架中
* @param stpLogic /
@@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.listener;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpLogic;
@@ -125,6 +126,12 @@ public interface SaTokenListener {
*/
default void doRegisterComponent(String compName, Object compObj) {}
/**
* 注册了自定义注解处理器
* @param handler 注解处理器
*/
default void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {}
/**
* StpLogic 对象替换
* @param stpLogic /
@@ -15,13 +15,14 @@
*/
package cn.dev33.satoken.listener;
import static cn.dev33.satoken.SaManager.log;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import static cn.dev33.satoken.SaManager.log;
/**
* Sa-Token 侦听器的一个实现:Log 打印
*
@@ -130,6 +131,17 @@ public class SaTokenListenerForLog implements SaTokenListener {
log.info("全局组件 {} 载入成功: {}", compName, canonicalName);
}
/**
* 注册了自定义注解处理器
* @param handler 注解处理器
*/
@Override
public void doRegisterAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
if(handler != null) {
log.info("注解扩展 @{} (处理器: {})", handler.getHandlerAnnotationClass().getSimpleName(), handler.getClass().getCanonicalName());
}
}
/**
* StpLogic 对象替换
* @param stpLogic /
@@ -96,7 +96,7 @@ public class SaSession implements SaSetValueInterface, Serializable {
/**
* 所有挂载数据
*/
private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
private Map<String, Object> dataMap = new ConcurrentHashMap<>();
// ----------------------- 构建相关
@@ -522,14 +522,26 @@ public class SaSession implements SaSetValueInterface, Serializable {
return dataMap;
}
/**
* 设置数据挂载集合 (改变底层对象引用,将 dataMap 整个对象替换)
* @param dataMap 数据集合
*
* @return 对象自身
*/
public SaSession setDataMap(Map<String, Object> dataMap) {
this.dataMap = dataMap;
return this;
}
/**
* 写入数据集合 (不改变底层对象引用,只将此 dataMap 所有数据进行替换)
* @param dataMap 数据集合
*/
public void refreshDataMap(Map<String, Object> dataMap) {
public SaSession refreshDataMap(Map<String, Object> dataMap) {
this.dataMap.clear();
this.dataMap.putAll(dataMap);
this.update();
return this;
}
//
@@ -300,6 +300,9 @@ public class SaLoginModel {
if(getTimeoutOrGlobalConfig() == SaTokenDao.NEVER_EXPIRE) {
return Integer.MAX_VALUE;
}
if (timeout > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
return (int)(long)timeout;
}
@@ -36,7 +36,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaValue2Box;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -590,6 +590,20 @@ public class StpLogic {
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id,建议的类型:(long | int | String
* @return 返回会话令牌
*/
public String getOrCreateLoginSession(Object id) {
String tokenValue = getTokenValueByLoginId(id);
if(tokenValue == null) {
tokenValue = createLoginSession(id, new SaLoginModel());
}
return tokenValue;
}
// --- 注销
/**
@@ -2114,7 +2128,7 @@ public class StpLogic {
// 如果该账号的 Account-Session 为 null,说明此账号尚没有客户端在登录,此时返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return Collections.emptyList();
return new ArrayList<>();
}
// 按照设备类型进行筛选
@@ -2132,7 +2146,7 @@ public class StpLogic {
// 如果该账号的 Account-Session 为 null,说明此账号尚没有客户端在登录,此时返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return Collections.emptyList();
return new ArrayList<>();
}
// 按照设备类型进行筛选
@@ -2233,79 +2247,6 @@ public class StpLogic {
public List<String> searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
return getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size, sortType);
}
// ------------------- 注解鉴权 -------------------
/**
* 根据注解 ( @SaCheckLogin ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckLogin at) {
this.checkLogin();
}
/**
* 根据注解 ( @SaCheckRole ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckRole at) {
String[] roleArray = at.value();
if(at.mode() == SaMode.AND) {
this.checkRoleAnd(roleArray);
} else {
this.checkRoleOr(roleArray);
}
}
/**
* 根据注解 ( @SaCheckPermission ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckPermission at) {
String[] permissionArray = at.value();
try {
if(at.mode() == SaMode.AND) {
this.checkPermissionAnd(permissionArray);
} else {
this.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : at.orRole()) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
/**
* 根据注解 ( @SaCheckSafe ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckSafe at) {
this.checkSafe(at.value());
}
/**
* 根据注解 ( @SaCheckDisable ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckDisable at) {
Object loginId = getLoginId();
for (String service : at.value()) {
this.checkDisableLevel(loginId, service, at.level());
}
}
// ------------------- 账号封禁 -------------------
@@ -2937,4 +2878,84 @@ public class StpLogic {
return false;
}
// ------------------- 过期方法 -------------------
/**
* 根据注解 ( @SaCheckLogin ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckLogin at) {
this.checkLogin();
}
/**
* 根据注解 ( @SaCheckRole ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckRole at) {
String[] roleArray = at.value();
if(at.mode() == SaMode.AND) {
this.checkRoleAnd(roleArray);
} else {
this.checkRoleOr(roleArray);
}
}
/**
* 根据注解 ( @SaCheckPermission ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckPermission at) {
String[] permissionArray = at.value();
try {
if(at.mode() == SaMode.AND) {
this.checkPermissionAnd(permissionArray);
} else {
this.checkPermissionOr(permissionArray);
}
} catch (NotPermissionException e) {
// 权限认证校验未通过,再开始角色认证校验
for (String role : at.orRole()) {
String[] rArr = SaFoxUtil.convertStringToArray(role);
// 某一项 role 认证通过,则可以提前退出了,代表通过
if (hasRoleAnd(rArr)) {
return;
}
}
throw e;
}
}
/**
* 根据注解 ( @SaCheckSafe ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckSafe at) {
this.checkSafe(at.value());
}
/**
* 根据注解 ( @SaCheckDisable ) 鉴权
*
* @param at 注解对象
*/
@Deprecated
public void checkByAnnotation(SaCheckDisable at) {
Object loginId = getLoginId();
for (String service : at.value()) {
this.checkDisableLevel(loginId, service, at.level());
}
}
}
@@ -224,7 +224,17 @@ public class StpUtil {
public static String createLoginSession(Object id, SaLoginModel loginModel) {
return stpLogic.createLoginSession(id, loginModel);
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id,建议的类型:(long | int | String
* @return 返回会话令牌
*/
public static String getOrCreateLoginSession(Object id) {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
/**
@@ -0,0 +1,133 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.strategy;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.annotation.handler.*;
import cn.dev33.satoken.fun.strategy.SaCheckMethodAnnotationFunction;
import cn.dev33.satoken.fun.strategy.SaGetAnnotationFunction;
import cn.dev33.satoken.fun.strategy.SaIsAnnotationPresentFunction;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token 注解鉴权相关策略
*
* @author click33
* @since 1.39.0
*/
public final class SaAnnotationStrategy {
private SaAnnotationStrategy() {
registerDefaultAnnotationHandler();
}
/**
* 全局单例引用
*/
public static final SaAnnotationStrategy instance = new SaAnnotationStrategy();
// ----------------------- 所有策略
/**
* 注解处理器集合
*/
public Map<Class<?>, SaAnnotationHandlerInterface<?>> annotationHandlerMap = new LinkedHashMap<>();
/**
* 注册所有默认的注解处理器
*/
public void registerDefaultAnnotationHandler() {
annotationHandlerMap.put(SaIgnore.class, new SaIgnoreHandler());
annotationHandlerMap.put(SaCheckLogin.class, new SaCheckLoginHandler());
annotationHandlerMap.put(SaCheckRole.class, new SaCheckRoleHandler());
annotationHandlerMap.put(SaCheckPermission.class, new SaCheckPermissionHandler());
annotationHandlerMap.put(SaCheckSafe.class, new SaCheckSafeHandler());
annotationHandlerMap.put(SaCheckDisable.class, new SaCheckDisableHandler());
annotationHandlerMap.put(SaCheckHttpBasic.class, new SaCheckHttpBasicHandler());
annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler());
annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler());
}
/**
* 注册一个注解处理器
*/
public void registerAnnotationHandler(SaAnnotationHandlerInterface<?> handler) {
annotationHandlerMap.put(handler.getHandlerAnnotationClass(), handler);
SaTokenEventCenter.doRegisterAnnotationHandler(handler);
}
/**
* 注册一个注解处理器,到首位
*/
public void registerAnnotationHandlerToFirst(SaAnnotationHandlerInterface<?> handler) {
Map<Class<?>, SaAnnotationHandlerInterface<?>> newMap = new LinkedHashMap<>();
newMap.put(handler.getHandlerAnnotationClass(), handler);
newMap.putAll(annotationHandlerMap);
this.annotationHandlerMap = newMap;
SaTokenEventCenter.doRegisterAnnotationHandler(handler);
}
/**
* 移除一个注解处理器
*/
public void removeAnnotationHandler(Class<?> cls) {
annotationHandlerMap.remove(cls);
}
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*/
@SuppressWarnings("unchecked")
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
// 遍历所有的注解处理器,检查此 method 是否具有这些指定的注解
for (Map.Entry<Class<?>, SaAnnotationHandlerInterface<?>> entry: annotationHandlerMap.entrySet()) {
// 先校验 Method 所属 Class 上的注解
Annotation classTakeAnnotation = instance.getAnnotation.apply(method.getDeclaringClass(), (Class<Annotation>)entry.getKey());
if(classTakeAnnotation != null) {
entry.getValue().check(classTakeAnnotation, method);
}
// 再校验 Method 上的注解
Annotation methodTakeAnnotation = instance.getAnnotation.apply(method, (Class<Annotation>)entry.getKey());
if(methodTakeAnnotation != null) {
entry.getValue().check(methodTakeAnnotation, method);
}
}
};
/**
* 从元素上获取注解
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*/
public SaIsAnnotationPresentFunction isAnnotationPresent = (method, annotationClass) -> {
return instance.getAnnotation.apply(method, annotationClass) != null ||
instance.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null;
};
}
@@ -16,19 +16,14 @@
package cn.dev33.satoken.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.strategy.*;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.httpauth.digest.SaHttpDigestUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
@@ -133,185 +128,6 @@ public final class SaStrategy {
return false;
};
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*/
public SaCheckMethodAnnotationFunction checkMethodAnnotation = (method) -> {
// 先校验 Method 所属 Class 上的注解
instance.checkElementAnnotation.accept(method.getDeclaringClass());
// 再校验 Method 上的注解
instance.checkElementAnnotation.accept(method);
};
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
*/
public SaCheckElementAnnotationFunction checkElementAnnotation = (element) -> {
// 校验 @SaCheckLogin 注解
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.instance.getAnnotation.apply(element, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type(), false).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
SaCheckRole checkRole = (SaCheckRole) SaStrategy.instance.getAnnotation.apply(element, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type(), false).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.instance.getAnnotation.apply(element, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type(), false).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.instance.getAnnotation.apply(element, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type(), false).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckDisable 注解
SaCheckDisable checkDisable = (SaCheckDisable) SaStrategy.instance.getAnnotation.apply(element, SaCheckDisable.class);
if(checkDisable != null) {
SaManager.getStpLogic(checkDisable.type(), false).checkByAnnotation(checkDisable);
}
// 校验 @SaCheckHttpBasic 注解
SaCheckHttpBasic checkHttpBasic = (SaCheckHttpBasic) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpBasic.class);
if(checkHttpBasic != null) {
SaHttpBasicUtil.check(checkHttpBasic.realm(), checkHttpBasic.account());
}
// 校验 @SaCheckHttpDigest 注解
SaCheckHttpDigest checkHttpDigest = (SaCheckHttpDigest) SaStrategy.instance.getAnnotation.apply(element, SaCheckHttpDigest.class);
if(checkHttpDigest != null) {
SaHttpDigestUtil.checkByAnnotation(checkHttpDigest);
}
// 校验 @SaCheckOr 注解
SaCheckOr checkOr = (SaCheckOr) SaStrategy.instance.getAnnotation.apply(element, SaCheckOr.class);
if(checkOr != null) {
SaStrategy.instance.checkOrAnnotation.accept(checkOr);
}
};
/**
* 对一个 @SaCheckOr 进行注解校验
*/
public SaCheckOrAnnotationFunction checkOrAnnotation = (at) -> {
// 记录校验过程中所有的异常
List<SaTokenException> errorList = new ArrayList<>();
// 逐个开始校验 >>>
// 1、校验注解:@SaCheckLogin
SaCheckLogin[] checkLoginArray = at.login();
for (SaCheckLogin item : checkLoginArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 2、校验注解:@SaCheckRole
SaCheckRole[] checkRoleArray = at.role();
for (SaCheckRole item : checkRoleArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 3、校验注解:@SaCheckPermission
SaCheckPermission[] checkPermissionArray = at.permission();
for (SaCheckPermission item : checkPermissionArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 4、校验注解:@SaCheckSafe
SaCheckSafe[] checkSafeArray = at.safe();
for (SaCheckSafe item : checkSafeArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 5、校验注解:@SaCheckDisable
SaCheckDisable[] checkDisableArray = at.disable();
for (SaCheckDisable item : checkDisableArray) {
try {
SaManager.getStpLogic(item.type(), false).checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 6、校验注解:@SaCheckBasic
SaCheckHttpBasic[] checkHttpBasicArray = at.httpBasic();
for (SaCheckHttpBasic item : checkHttpBasicArray) {
try {
SaHttpBasicUtil.check(item.realm(), item.account());
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 7、校验注解:@SaCheckDigest
SaCheckHttpDigest[] checkHttpDigestArray = at.httpDigest();
for (SaCheckHttpDigest item : checkHttpDigestArray) {
try {
SaHttpDigestUtil.checkByAnnotation(item);
return;
} catch (SaTokenException e) {
errorList.add(e);
}
}
// 如果执行到这里,有两种可能:
// 可能 1. SaCheckOr 注解上不包含任何注解校验,此时 errorList 里面一个异常都没有,我们直接跳过即可
// 可能 2. 所有注解校验都通过不了,此时 errorList 里面会有多个异常,我们随便抛出一个即可
if(errorList.size() == 0) {
// return;
} else {
throw errorList.get(0);
}
};
/**
* 从元素上获取注解
*/
public SaGetAnnotationFunction getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*/
public SaIsAnnotationPresentFunction isAnnotationPresent = (method, annotationClass) -> {
return instance.getAnnotation.apply(method, annotationClass) != null ||
instance.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null;
};
/**
* 生成唯一式 token 的算法
*/
@@ -427,62 +243,6 @@ public final class SaStrategy {
return this;
}
/**
* 对一个 [Method] 对象进行注解校验 (注解鉴权内部实现)
*
* @param checkMethodAnnotation /
* @return /
*/
public SaStrategy setCheckMethodAnnotation(SaCheckMethodAnnotationFunction checkMethodAnnotation) {
this.checkMethodAnnotation = checkMethodAnnotation;
return this;
}
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
*
* @param checkElementAnnotation /
* @return /
*/
public SaStrategy setCheckElementAnnotation(SaCheckElementAnnotationFunction checkElementAnnotation) {
this.checkElementAnnotation = checkElementAnnotation;
return this;
}
/**
* 对一个 @SaCheckOr 进行注解校验
* <p> 参数 [SaCheckOr 注解的实例]
*
* @param checkOrAnnotation /
* @return /
*/
public SaStrategy setCheckOrAnnotation(SaCheckOrAnnotationFunction checkOrAnnotation) {
this.checkOrAnnotation = checkOrAnnotation;
return this;
}
/**
* 从元素上获取注解
*
* @param getAnnotation /
* @return /
*/
public SaStrategy setGetAnnotation(SaGetAnnotationFunction getAnnotation) {
this.getAnnotation = getAnnotation;
return this;
}
/**
* 判断一个 Method 或其所属 Class 是否包含指定注解
*
* @param isAnnotationPresent /
* @return /
*/
public SaStrategy setIsAnnotationPresent(SaIsAnnotationPresentFunction isAnnotationPresent) {
this.isAnnotationPresent = isAnnotationPresent;
return this;
}
/**
* 生成唯一式 token 的算法
*
@@ -29,7 +29,6 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
/**
* Sa-Token 内部工具类
@@ -76,6 +75,17 @@ public class SaFoxUtil {
return sb.toString();
}
/**
* 生成指定区间的 int 值
*
* @param min 最小值(包括)
* @param max 最大值(包括)
* @return /
*/
public static int getRandomNumber(int min, int max) {
return ThreadLocalRandom.current().nextInt(min, max + 1);
}
/**
* 指定元素是否为null或者空字符串
* @param str 指定元素
@@ -96,14 +106,35 @@ public class SaFoxUtil {
/**
* 指定数组是否为null或者空数组
* <h3> 该方法已过时,建议使用 isEmptyArray 方法 </h3>
* @param <T> /
* @param array /
* @return /
*/
@Deprecated
public static <T> boolean isEmpty(T[] array) {
return isEmptyArray(array);
}
/**
* 指定数组是否为null或者空数组
* @param <T> /
* @param array /
* @return /
*/
public static <T> boolean isEmptyArray(T[] array) {
return array == null || array.length == 0;
}
/**
* 指定集合是否为null或者空数组
* @param list /
* @return /
*/
public static boolean isEmptyList(List<?> list) {
return list == null || list.isEmpty();
}
/**
* 比较两个对象是否相等
* @param a 第一个对象
@@ -563,7 +594,7 @@ public class SaFoxUtil {
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
if(list == null || list.isEmpty()) {
return "";
}
StringBuilder str = new StringBuilder();
@@ -616,6 +647,15 @@ public class SaFoxUtil {
return new ArrayList<>(Arrays.asList(str));
}
/**
* String 集合转数组
* @param list 集合
* @return 数组
*/
public static String[] toArray(List<String> list) {
return list.toArray(new String[0]);
}
public static List<String> logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal");
/**
@@ -679,4 +719,63 @@ public class SaFoxUtil {
return false;
}
/**
* list1 是否完全包含 list2 中所有元素
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static boolean list1ContainList2AllElement(List<String> list1, List<String> list2){
if(list2 == null || list2.isEmpty()) {
return true;
}
if(list1 == null || list1.isEmpty()) {
return false;
}
for (String str : list2) {
if(!list1.contains(str)) {
return false;
}
}
return true;
}
/**
* list1 是否包含 list2 中任意一个元素
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static boolean list1ContainList2AnyElement(List<String> list1, List<String> list2){
if(list1 == null || list1.isEmpty() || list2 == null || list2.isEmpty()) {
return false;
}
for (String str : list2) {
if(list1.contains(str)) {
return true;
}
}
return false;
}
/**
* 从 list1 中剔除 list2 所包含的元素 (克隆副本操作,不影响 list1)
* @param list1 集合1
* @param list2 集合2
* @return /
*/
public static List<String> list1RemoveByList2(List<String> list1, List<String> list2){
if(list1 == null) {
return null;
}
if(list1.isEmpty() || list2 == null || list2.isEmpty()) {
return new ArrayList<>(list1);
}
List<String> listX = new ArrayList<>(list1);
for (String str : list2) {
listX.remove(str);
}
return listX;
}
}
@@ -15,6 +15,8 @@
*/
package cn.dev33.satoken.util;
import cn.dev33.satoken.SaManager;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -150,7 +152,42 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
}
return this;
}
/**
* 写入一个 json 字符串, 连缀风格
* @param jsonString json 字符串
* @return 对象自身
*/
public SaResult setJsonString(String jsonString) {
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(jsonString);
return setMap(map);
}
/**
* 移除默认属性(code、msg、data), 连缀风格
* @return 对象自身
*/
public SaResult removeDefaultFields() {
this.remove("code");
this.remove("msg");
this.remove("data");
return this;
}
/**
* 移除非默认属性(code、msg、data), 连缀风格
* @return 对象自身
*/
public SaResult removeNonDefaultFields() {
for (String key : this.keySet()) {
if("code".equals(key) || "msg".equals(key) || "data".equals(key)) {
continue;
}
this.remove(key);
}
return this;
}
// ============================ 静态方法快速构建 ==================================
@@ -180,7 +217,11 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
public static SaResult get(int code, String msg, Object data) {
return new SaResult(code, msg, data);
}
// 构建一个空的
public static SaResult empty() {
return new SaResult();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
@@ -36,7 +36,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.38.0";
public static final String VERSION_NO = "v1.39.0";
/**
* Sa-Token 开源地址 Gitee
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -73,7 +73,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>1.38.0</version>
<version>1.39.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
+2 -2
View File
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -51,7 +51,7 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -1,10 +1,9 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 示例
* @author click33
@@ -1,10 +1,9 @@
package com.pj.cases.test;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用 Controller
* @author click33
@@ -17,14 +16,14 @@ public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
System.out.println("------------进来了");
return SaResult.ok();
}
// 测试 浏览器访问: http://localhost:8081/test/test2
@RequestMapping("test2")
public SaResult test2() {
System.out.println("------------进来了");
System.out.println("------------进来了");
return SaResult.ok();
}
@@ -51,25 +51,25 @@ public class SecureController {
}
// RSA加密 ---- http://localhost:8081/secure/rsa
@RequestMapping("rsa")
public SaResult rsa() {
// 定义私钥和公钥
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";
// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);
// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2);
return SaResult.ok();
}
// @RequestMapping("rsa")
// public SaResult rsa() {
// // 定义私钥和公钥
// String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
// String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
//
// // 文本
// String text = "Sa-Token 一个轻量级java权限认证框架";
//
// // 使用公钥加密
// String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
// System.out.println("公钥加密后:" + ciphertext);
//
// // 使用私钥解密
// String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
// System.out.println("私钥解密后:" + text2);
//
// return SaResult.ok();
// }
// Base64 编码 ---- http://localhost:8081/secure/base64
@RequestMapping("base64")
@@ -4,7 +4,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotBasicAuthException;
import cn.dev33.satoken.exception.NotHttpBasicAuthException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
@@ -57,8 +57,8 @@ public class GlobalException {
}
// 拦截:Http Basic 校验失败异常
@ExceptionHandler(NotBasicAuthException.class)
public SaResult handlerException(NotBasicAuthException e) {
@ExceptionHandler(NotHttpBasicAuthException.class)
public SaResult handlerException(NotHttpBasicAuthException e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
@@ -5,7 +5,7 @@ import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,14 +32,14 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor(handle -> {
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
// 指定一条 match 规则
SaRouter
.match("/user/**") // 拦截的 path 列表,可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表,可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
.match("/user/**") // 拦截的 path 列表,可以写多个
.notMatch("/user/doLogin", "/user/doLogin2") // 排除掉的 path 列表,可以写多个
.check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式
// 权限校验 -- 不同模块认证不同权限
// 权限校验 -- 不同模块认证不同权限
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
@@ -49,16 +49,16 @@ public class SaTokenConfigure implements WebMvcConfigurer {
// 甚至你可以随意的写一个打印语句
SaRouter.match("/router/print", r -> System.out.println("----啦啦啦----"));
// 写一个完整的 lambda
// 写一个完整的 lambda
SaRouter.match("/router/print2", r -> {
System.out.println("----啦啦啦2----");
// ... 其它代码
// ... 其它代码
});
/*
* 相关路由都定义在 com.pj.cases.use.RouterCheckController 中
* 相关路由都定义在 com.pj.cases.use.RouterCheckController 中
*/
})).addPathPatterns("/**");
}
@@ -117,7 +117,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
@PostConstruct
public void rewriteSaStrategy() {
// 重写Sa-Token的注解处理器,增加注解合并功能
SaStrategy.instance.getAnnotation = (element, annotationClass) -> {
SaAnnotationStrategy.instance.getAnnotation = (element, annotationClass) -> {
return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
};
}
@@ -215,6 +215,16 @@ public class StpUserUtil {
return stpLogic.createLoginSession(id, loginModel);
}
/**
* 获取指定账号 id 的登录会话数据,如果获取不到则创建并返回
*
* @param id 账号id,建议的类型:(long | int | String
* @return 返回会话令牌
*/
public static String getOrCreateLoginSession(Object id) {
return stpLogic.getOrCreateLoginSession(id);
}
// --- 注销
/**
@@ -0,0 +1,33 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface CheckAccount {
/**
* 需要校验的账号
*
* @return /
*/
String name();
/**
* 需要校验的密码
*
* @return /
*/
String pwd();
}
@@ -0,0 +1,18 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录认证(User版):只有登录之后才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckLogin {
}
@@ -0,0 +1,50 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.annotation.SaMode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证(User版):必须具有指定权限才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckPermission {
/**
* 需要校验的权限码
* @return 需要校验的权限码
*/
String [] value() default {};
/**
* 验证模式:AND | OR,默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
/**
* 在权限校验不通过时的次要选择,两者只要其一校验成功即可通过校验
*
* <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>
*
* @return /
*/
String[] orRole() default {};
}
@@ -0,0 +1,32 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.annotation.SaMode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色认证(User版):必须具有指定角色标识才能进入该方法
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface SaUserCheckRole {
/**
* 需要校验的角色标识
* @return 需要校验的角色标识
*/
String [] value() default {};
/**
* 验证模式:AND | OR,默认AND
* @return 验证模式
*/
SaMode mode() default SaMode.AND;
}
@@ -0,0 +1,28 @@
package com.pj.satoken.custom_annotation;
import cn.dev33.satoken.util.SaTokenConsts;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)。
*
* @author click33
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaUserCheckSafe {
/**
* 要校验的服务
*
* @return /
*/
String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE;
}
@@ -0,0 +1,42 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import com.pj.satoken.custom_annotation.CheckAccount;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 CheckAccount 的处理器
*
* @author click33
*
*/
@Component
public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> getHandlerAnnotationClass() {
return CheckAccount.class;
}
// 每次请求校验注解时,会执行的方法
@Override
public void checkMethod(CheckAccount at, Method method) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
// 与注解中指定的值相比较
if(name.equals(at.name()) && pwd.equals(at.pwd()) ) {
// 校验通过,什么也不做
} else {
// 校验不通过,则抛出异常
throw new SaTokenException("账号或密码错误,未通过校验");
}
}
}
@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckLoginHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckLogin;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckLogin 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckLoginHandler implements SaAnnotationHandlerInterface<SaUserCheckLogin> {
@Override
public Class<SaUserCheckLogin> getHandlerAnnotationClass() {
return SaUserCheckLogin.class;
}
@Override
public void checkMethod(SaUserCheckLogin at, Method method) {
SaCheckLoginHandler._checkMethod(StpUserUtil.TYPE);
}
}
@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckPermissionHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckPermission;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckPermission 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckPermissionHandler implements SaAnnotationHandlerInterface<SaUserCheckPermission> {
@Override
public Class<SaUserCheckPermission> getHandlerAnnotationClass() {
return SaUserCheckPermission.class;
}
@Override
public void checkMethod(SaUserCheckPermission at, Method method) {
SaCheckPermissionHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode(), at.orRole());
}
}
@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckRoleHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckRole;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckRole 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckRoleHandler implements SaAnnotationHandlerInterface<SaUserCheckRole> {
@Override
public Class<SaUserCheckRole> getHandlerAnnotationClass() {
return SaUserCheckRole.class;
}
@Override
public void checkMethod(SaUserCheckRole at, Method method) {
SaCheckRoleHandler._checkMethod(StpUserUtil.TYPE, at.value(), at.mode());
}
}
@@ -0,0 +1,29 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.annotation.handler.SaCheckSafeHandler;
import com.pj.satoken.StpUserUtil;
import com.pj.satoken.custom_annotation.SaUserCheckSafe;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 注解 SaUserCheckPermission 的处理器
*
* @author click33
*/
@Component
public class SaUserCheckSafeHandler implements SaAnnotationHandlerInterface<SaUserCheckSafe> {
@Override
public Class<SaUserCheckSafe> getHandlerAnnotationClass() {
return SaUserCheckSafe.class;
}
@Override
public void checkMethod(SaUserCheckSafe at, Method method) {
SaCheckSafeHandler._checkMethod(StpUserUtil.TYPE, at.value());
}
}
@@ -1,13 +1,13 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.pj.satoken.StpUserUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.pj.satoken.StpUserUtil;
/**
* 登录认证(User版)只有登录之后才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
@@ -1,16 +1,15 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
/**
* 权限认证(User版)必须具有指定权限才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
@@ -35,5 +34,23 @@ public @interface SaUserCheckPermission {
*/
@AliasFor(annotation = SaCheckPermission.class)
SaMode mode() default SaMode.AND;
/**
* 在权限校验不通过时的次要选择两者只要其一校验成功即可通过校验
*
* <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>
*
* @return /
*/
@AliasFor(annotation = SaCheckPermission.class)
String[] orRole() default {};
}
@@ -1,16 +1,15 @@
package com.pj.satoken.at;
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.pj.satoken.StpUserUtil;
import org.springframework.core.annotation.AliasFor;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
/**
* 角色认证(User版)必须具有指定角色标识才能进入该方法
* <p> 可标注在函数类上效果等同于标注在此类的所有方法上
@@ -0,0 +1,31 @@
package com.pj.satoken.merge_annotation;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.util.SaTokenConsts;
import com.pj.satoken.StpUserUtil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 二级认证校验(User版):客户端必须完成二级认证之后,才能进入该方法,否则将被抛出异常。
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)。
*
* @author click33
*/
@SaCheckSafe(type = StpUserUtil.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaUserCheckSafe {
/**
* 要校验的服务
*
* @return /
*/
String value() default SaTokenConsts.DEFAULT_SAFE_AUTH_SERVICE;
}
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>
@@ -17,7 +17,7 @@
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>
+1 -1
View File
@@ -27,7 +27,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.10</lombok.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -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.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -1,17 +1,17 @@
package com.pj.oauth2;
import javax.servlet.http.HttpServletRequest;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.utils.SoMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.ejlchina.okhttps.OkHttps;
import com.pj.utils.SoMap;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import javax.servlet.http.HttpServletRequest;
/**
* Sa-OAuth2 Client端 控制器
@@ -21,10 +21,10 @@ import cn.dev33.satoken.util.SaResult;
public class SaOAuthClientController {
// 相关参数配置
private String clientId = "1001"; // 应用id
private String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
private String serverUrl = "http://sa-oauth-server.com:8001"; // 服务端接口
private final String clientId = "1001"; // 应用id
private final String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
private final String serverUrl = "http://sa-oauth-server.com:8000"; // 服务端接口
// 进入首页
@RequestMapping("/")
public Object index(HttpServletRequest request) {
@@ -34,7 +34,7 @@ public class SaOAuthClientController {
// 根据Code码进行登录,获取 Access-Token 和 openid
@RequestMapping("/codeLogin")
public SaResult codeLogin(String code) {
public SaResult codeLogin(String code) throws JsonProcessingException {
// 调用Server端接口,获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code")
@@ -45,26 +45,25 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 根据openid获取其对应的userId
long uid = getUserIdByOpenid(so.getString("openid"));
so.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
return SaResult.data(so);
}
// 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh")
public SaResult refresh(String refreshToken) {
public SaResult refresh(String refreshToken) throws JsonProcessingException {
// 调用Server端接口,通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token")
@@ -75,21 +74,20 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Access-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数
return SaResult.data(so);
}
// 模式三:密码式-授权登录
@RequestMapping("/passwordLogin")
public SaResult passwordLogin(String username, String password) {
public SaResult passwordLogin(String username, String password) throws JsonProcessingException {
// 模式三:密码式-授权登录
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "password")
@@ -101,26 +99,25 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 根据openid获取其对应的userId
SoMap data = so.getMap("data");
long uid = getUserIdByOpenid(data.getString("openid"));
data.set("uid", uid);
// 根据openid获取其对应的userId
long uid = getUserIdByOpenid(so.getString("openid"));
so.set("uid", uid);
// 返回相关参数
StpUtil.login(uid);
return SaResult.data(data);
return SaResult.data(so);
}
// 模式四:获取应用的 Client-Token
@RequestMapping("/clientToken")
public SaResult clientToken() {
public SaResult clientToken() throws JsonProcessingException {
// 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials")
@@ -130,16 +127,15 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=新的Client-Token )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数
return SaResult.data(so);
}
// 注销登录
@@ -151,7 +147,7 @@ public class SaOAuthClientController {
// 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息
@RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) {
public SaResult getUserinfo(String accessToken) throws JsonProcessingException {
// 调用Server端接口,查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken)
@@ -159,16 +155,15 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
return SaResult.error(so.getString("msg"));
}
// 返回相关参数 (data=获取到的资源 )
SoMap data = so.getMap("data");
return SaResult.data(data);
// 返回相关参数 (data=获取到的资源 )
return SaResult.data(so);
}
// 全局异常拦截
@@ -42,33 +42,33 @@
<h3>模式一:授权码(Authorization Code</h3>
<p class="pst">授权码:OAuth2.0标准授权流程,先 (重定向) 获取Code授权码,再 (Rest API) 获取 Access-Token 和 Openid </p>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<button>点我开始授权登录(静默授权)</button>
</a>
<span class="ps">当请求链接不包含scope权限时,将无需用户手动确认,做到静默授权,当然此时我们也只能获取openid</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<span class="ps">当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权</span>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userinfo">
<button>授权登录(显式授权)</button>
</a>
<span class="ps">当请求链接包含具体的scope权限时,将需要用户手动确认,此时我们除了openid以外还可以获取更多的资源</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<span class="ps">当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据</span>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userinfo</code>
<button onclick="refreshToken()">刷新令牌</button>
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废</span>
<code>http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<button onclick="getUserinfo()">获取账号信息</button>
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) </span>
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/userinfo?access_token={value}</code>
<br>
<h3>模式二:隐藏式(Implicit</h3>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>隐藏式</button>
</a>
<span class="ps">越过授权码的步骤,直接返回token到前端页面( 格式:http//:domain.com#token=xxxx-xxxx </span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<br>
<h3>模式三:密码式(Password</h3>
@@ -76,18 +76,18 @@
账号:<input name="username">
密码:<input name="password">
<button onclick="passwordLogin()">登录</button>
<code>http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<br>
<h3>模式四:凭证式(Client Credentials</h3>
<p class="pst">以上三种模式获取的都是用户的 Access-Token,代表用户对第三方应用的授权,在OAuth2.0中还有一种针对 Client级别的授权,
即:Client-Token,代表应用自身的资源授权</p>
<p class="pst">Client-Token具有延迟作废特性,即:在每次获取最新Client-Token的时候,旧Client-Token不会立即过期,而是作为Past-Token再次
<p class="pst">Client-Token具有延迟作废特性,即:在每次获取最新Client-Token的时候,旧Client-Token不会立即过期,而是作为Lower-Client-Token再次
储存起来,资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”,
保证了服务的高可用</p>
<button onclick="getClientToken()">获取应用Client-Token</button>
<code>http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<br><br>
<span>更多资料请参考 Sa-Token 官方文档地址:</span>
@@ -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.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -58,13 +58,20 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 热刷新 -->
<!-- sa-token-jwt 签发 OIDC id_token 令牌 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 热刷新 -->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency>
</dependency>-->
<!-- ConfigurationProperties -->
<dependency>
@@ -1,18 +1,20 @@
package com.pj;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动:Sa-OAuth2 Server端
* @author click33
* 启动:Sa-OAuth2 Server端
* @author click33
*/
@SpringBootApplication
@SpringBootApplication
public class SaOAuth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaOAuth2ServerApplication.class, args);
System.out.println("\nSa-Token-OAuth Server端启动成功");
System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:");
System.out.println(SaOAuth2Manager.getServerConfig());
}
}
@@ -0,0 +1,46 @@
package com.pj.oauth2;
import cn.dev33.satoken.oauth2.consts.GrantType;
import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
import org.springframework.stereotype.Component;
/**
* Sa-Token OAuth2:自定义数据加载器
*
* @author click33
*/
@Component
public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
// 根据 clientId 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("1001") // client id
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥
.addAllowRedirectUris("*") // 所有允许授权的 url
.addContractScopes("openid", "userid", "userinfo", "oidc") // 所有签约的权限
.addAllowGrantTypes( // 所有允许的授权模式
GrantType.authorization_code, // 授权码式
GrantType.implicit, // 隐式式
GrantType.refresh_token, // 刷新令牌
GrantType.password, // 密码式
GrantType.client_credentials, // 客户端模式
"phone_code" // 自定义授权模式 手机号验证码登录
)
;
}
return null;
}
// 根据 clientId 和 loginId 获取 openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询
return SaOAuth2DataLoader.super.getOpenid(clientId, loginId);
}
}
@@ -1,92 +1,86 @@
package com.pj.oauth2;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig;
import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor;
import cn.dev33.satoken.oauth2.template.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
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 java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Handle;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-OAuth2 Server端 控制器
* Sa-Token-OAuth2 Server端 Controller
*
* @author click33
*
*/
@RestController
public class SaOAuth2ServerController {
// 处理所有OAuth相关请求
// OAuth2-Server 端:处理所有 OAuth2 相关请求
@RequestMapping("/oauth2/*")
public Object request() {
System.out.println("------- 进入请求: " + SaHolder.getRequest().getUrl());
return SaOAuth2Handle.serverRequest();
}
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
cfg.
// 未登录的视图
setNotLoginView(()->{
return new ModelAndView("login.html");
}).
// 登录处理函数
setDoLoginHandle((name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
// 授权确认视图
setConfirmView((clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
})
;
return SaOAuth2ServerProcessor.instance.dister();
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
// Sa-Token OAuth2 定制化配置
@Autowired
public void configOAuth2Server(SaOAuth2ServerConfig oauth2Server) {
// 未登录的视图
oauth2Server.notLoginView = ()->{
return new ModelAndView("login.html");
};
// 登录处理函数
oauth2Server.doLoginHandle = (name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
};
// 授权确认视图
oauth2Server.confirmView = (clientId, scopes)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scopes);
return new ModelAndView("confirm.html", map);
};
}
// ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------
// 获取Userinfo信息:昵称、头像、性别等等
// 获取 userinfo 信息:昵称、头像、性别等等
@RequestMapping("/oauth2/userinfo")
public SaResult userinfo() {
// 获取 Access-Token 对应的账号id
String accessToken = SaHolder.getRequest().getParamNotNull("access_token");
// 获取 Access-Token 对应的账号id
String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest());
Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("-------- 此Access-Token对应的账号id: " + loginId);
// 校验 Access-Token 是否具有权限: userinfo
SaOAuth2Util.checkScope(accessToken, "userinfo");
SaOAuth2Util.checkAccessTokenScope(accessToken, "userinfo");
// 模拟账号信息 (真实环境需要查询数据库获取信息)
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("nickname", "shengzhang_");
Map<String, Object> map = new LinkedHashMap<>();
// map.put("userId", loginId); 一般原则下,oauth2-server 不能把 userId 返回给 oauth2-client
map.put("nickname", "林小林");
map.put("avatar", "http://xxx.com/1.jpg");
map.put("age", "18");
map.put("sex", "");
map.put("address", "山东省 青岛市 城阳区");
return SaResult.data(map);
return SaResult.ok().setMap(map);
}
}
@@ -1,39 +0,0 @@
package com.pj.oauth2;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.model.SaClientModel;
/**
* Sa-Token OAuth2.0 整合实现
* @author click33
*/
@Component
public class SaOAuth2TemplateImpl extends SaOAuth2Template {
// 根据 id 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
.setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo")
.setIsAutoMode(true);
}
return null;
}
// 根据ClientId 和 LoginId 获取openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此为模拟数据,真实环境需要从数据库查询
return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
}
// -------------- 其它需要重写的函数
}
@@ -0,0 +1,32 @@
//package com.pj.oauth2.custom;
//
//import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel;
//import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler;
//import org.springframework.stereotype.Component;
//
///**
// * 扩展 OIDC 权限处理器,返回更多字段
// *
// * @author click33
// * @since 2024/8/24
// */
//@Component
//public class CustomOidcScopeHandler extends OidcScopeHandler {
//
// @Override
// public IdTokenModel workExtraData(IdTokenModel idToken) {
// Object userId = idToken.sub;
// System.out.println("----- 为 idToken 追加扩展字段 ----- ");
//
// idToken.extraData.put("uid", userId); // 用户id
// idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称
// idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像
// idToken.extraData.put("email", "456456@xx.com"); // 邮箱
// idToken.extraData.put("phone_number", "13144556677"); // 手机号
// // 更多字段 ...
// // 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
//
// return idToken;
// }
//
//}
@@ -0,0 +1,57 @@
//package com.pj.oauth2.custom;
//
//import cn.dev33.satoken.SaManager;
//import cn.dev33.satoken.context.model.SaRequest;
//import cn.dev33.satoken.oauth2.SaOAuth2Manager;
//import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
//import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel;
//import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
//import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface;
//import org.springframework.stereotype.Component;
//
//import java.util.List;
//
///**
// * 自定义 phone_code 授权模式处理器
// *
// * @author click33
// * @since 2024/8/23
// */
//@Component
//public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface {
//
// @Override
// public String getHandlerGrantType() {
// return "phone_code";
// }
//
// @Override
// public AccessTokenModel getAccessToken(SaRequest req, String clientId, List<String> scopes) {
//
// // 获取前端提交的参数
// String phone = req.getParamNotNull("phone");
// String code = req.getParamNotNull("code");
// String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone);
//
// // 1、校验验证码是否正确
// if(!code.equals(realCode)) {
// throw new SaOAuth2Exception("验证码错误");
// }
//
// // 2、校验通过,删除验证码
// SaManager.getSaTokenDao().delete("phone_code:" + phone);
//
// // 3、登录
// long userId = 10001; // 模拟 userId,真实项目应该根据手机号从数据库查询
//
// // 4、构建 ra 对象
// RequestAuthModel ra = new RequestAuthModel();
// ra.clientId = clientId;
// ra.loginId = userId;
// ra.scopes = scopes;
//
// // 5、生成 Access-Token
// AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true);
// return at;
// }
//}
@@ -0,0 +1,26 @@
//package com.pj.oauth2.custom;
//
//import cn.dev33.satoken.SaManager;
//import cn.dev33.satoken.util.SaFoxUtil;
//import cn.dev33.satoken.util.SaResult;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
///**
// * 自定义手机登录接口
// *
// * @author click33
// * @since 2024/8/23
// */
//@RestController
//public class PhoneLoginController {
//
// @RequestMapping("/oauth2/sendPhoneCode")
// public SaResult sendCode(String phone) {
// String code = SaFoxUtil.getRandomNumber(100000, 999999) + "";
// SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5);
// System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功");
// return SaResult.ok("验证码发送成功");
// }
//
//}
@@ -0,0 +1,41 @@
//package com.pj.oauth2.custom;
//
//import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
//import cn.dev33.satoken.oauth2.data.model.ClientTokenModel;
//import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeHandlerInterface;
//import org.springframework.stereotype.Component;
//
//import java.util.LinkedHashMap;
//import java.util.Map;
//
///**
// * @author click33
// * @since 2024/8/20
// */
//@Component
//public class UserinfoScopeHandler implements SaOAuth2ScopeHandlerInterface {
//
// @Override
// public String getHandlerScope() {
// return "userinfo";
// }
//
// @Override
// public void workAccessToken(AccessTokenModel at) {
// System.out.println("--------- userinfo 权限,加工 AccessTokenModel --------- ");
// // 模拟账号信息 (真实环境需要查询数据库获取信息)
// Map<String, Object> map = new LinkedHashMap<String, Object>();
// map.put("userId", "10008");
// map.put("nickname", "shengzhang_");
// map.put("avatar", "http://xxx.com/1.jpg");
// map.put("age", "18");
// map.put("sex", "男");
// map.put("address", "山东省 青岛市 城阳区");
// at.extraData.put("userinfo", map);
// }
//
// @Override
// public void workClientToken(ClientTokenModel ct) {
// }
//
//}
@@ -0,0 +1,22 @@
package com.pj.satoken;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
@@ -0,0 +1,27 @@
package com.pj.satoken;
import cn.dev33.satoken.interceptor.SaInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器打开注解鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器打开注解鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}
@@ -0,0 +1,72 @@
package com.pj.test;
import cn.dev33.satoken.oauth2.annotation.SaCheckAccessToken;
import cn.dev33.satoken.oauth2.annotation.SaCheckClientIdSecret;
import cn.dev33.satoken.oauth2.annotation.SaCheckClientToken;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* OAuth2 相关注解测试 Controller
*
* @author click33
* @since 2024/8/25
*/
@RestController
@RequestMapping("/test")
public class TestController {
// 测试:携带有效的 access_token 才可以进入请求
// 你可以在请求参数中携带 access_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带
@SaCheckAccessToken
@RequestMapping("/checkAccessToken")
public SaResult checkAccessToken() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 access_token ,并且具备指定 scope 才可以进入请求
@SaCheckAccessToken(scope = "userinfo")
@RequestMapping("/checkAccessTokenScope")
public SaResult checkAccessTokenScope() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 access_token ,并且具备指定 scope 列表才可以进入请求
@SaCheckAccessToken(scope = {"openid", "userinfo"})
@RequestMapping("/checkAccessTokenScopeList")
public SaResult checkAccessTokenScopeList() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 client_token 才可以进入请求
// 你可以在请求参数中携带 client_token 参数,或者从请求头以 Authorization: bearer xxx 的形式携带
@SaCheckClientToken
@RequestMapping("/checkClientToken")
public SaResult checkClientToken() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 client_token ,并且具备指定 scope 才可以进入请求
@SaCheckClientToken(scope = "userinfo")
@RequestMapping("/checkClientTokenScope")
public SaResult checkClientTokenScope() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 client_token ,并且具备指定 scope 列表才可以进入请求
@SaCheckClientToken(scope = {"openid", "userinfo"})
@RequestMapping("/checkClientTokenScopeList")
public SaResult checkClientTokenScopeList() {
return SaResult.ok("访问成功");
}
// 测试:携带有效的 client_id 和 client_secret 信息,才可以进入请求
// 你可以在请求参数中携带 client_id 和 client_secret 参数,或者从请求头以 Authorization: Basic base64(client_id:client_secret) 的形式携带
@SaCheckClientIdSecret
@RequestMapping("/checkClientIdSecret")
public SaResult checkClientIdSecret() {
return SaResult.ok("访问成功");
}
}
@@ -1,19 +1,31 @@
server:
port: 8001
server:
port: 8000
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken-server
# OAuth2.0 配置
oauth2:
is-code: true
is-implicit: true
is-password: true
is-client: true
spring:
# redis配置
sa-token:
# token名称 (同时也是 Cookie 名称)
token-name: satoken
# 是否打印操作日志
is-log: true
# jwt 秘钥
jwt-secret-key: saxsaxsaxsax
# OAuth2.0 配置
oauth2-server:
# 是否全局开启授权码模式
enable-authorization-code: true
# 是否全局开启 Implicit 模式
enable-implicit: true
# 是否全局开启密码模式
enable-password: true
# 是否全局开启客户端模式
enable-client-credentials: true
# 定义哪些 scope 是高级权限,多个用逗号隔开
# higher-scope: openid,userid
# 定义哪些 scope 是低级权限,多个用逗号隔开
# lower-scope: userinfo
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 1
@@ -22,7 +34,7 @@ spring:
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# password:
# 连接超时时间(毫秒)
timeout: 1000ms
lettuce:
@@ -35,6 +47,5 @@ spring:
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
@@ -33,20 +33,31 @@
console.log('-----------');
$.ajax({
url: '/oauth2/doConfirm',
method: "POST",
data: {
client_id: getParam('client_id'),
scope: getParam('scope')
scope: getParam('scope'),
// 以下四个参数必须一起出现
build_redirect_uri: true,
response_type: getParam('response_type'),
redirect_uri: getParam('redirect_uri'),
state: getParam('state'),
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
console.log('res', res);
if(res.code === 200) {
layer.msg('授权成功!');
setTimeout(function() {
location.reload(true);
if (res.redirect_uri) {
location.href = res.redirect_uri;
} else {
location.reload();
}
}, 800);
} else {
// 重定向至授权失败URL
layer.alert('授权失败');
layer.alert('授权失败:' + res.msg);
}
},
error: function(e) {
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
</parent>
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
+5 -2
View File
@@ -10,13 +10,16 @@
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.7.0</version>
<version>2.9.1</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.traget>17</maven.compiler.traget>
<sa-token.version>1.39.0</sa-token.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
@@ -0,0 +1,33 @@
package com.pj.satoken.custom_annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 账号校验:在标注一个方法上时,要求前端必须提交相应的账号密码参数才能访问方法。
*
* @author click33
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface CheckAccount {
/**
* 需要校验的账号
*
* @return /
*/
String name();
/**
* 需要校验的密码
*
* @return /
*/
String pwd();
}
@@ -0,0 +1,42 @@
package com.pj.satoken.custom_annotation.handler;
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import com.pj.satoken.custom_annotation.CheckAccount;
import org.noear.solon.annotation.Component;
import java.lang.reflect.Method;
/**
* 注解 CheckAccount 的处理器
*
* @author click33
*
*/
@Component
public class CheckAccountHandler implements SaAnnotationHandlerInterface<CheckAccount> {
// 指定这个处理器要处理哪个注解
@Override
public Class<CheckAccount> getHandlerAnnotationClass() {
return CheckAccount.class;
}
// 每次请求校验注解时,会执行的方法
@Override
public void checkMethod(CheckAccount at, Method method) {
// 获取前端请求提交的参数
String name = SaHolder.getRequest().getParamNotNull("name");
String pwd = SaHolder.getRequest().getParamNotNull("pwd");
// 与注解中指定的值相比较
if(name.equals(at.name()) && pwd.equals(at.pwd()) ) {
// 校验通过,什么也不做
} else {
// 校验不通过,则抛出异常
throw new SaTokenException("账号或密码错误,未通过校验");
}
}
}
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -1,14 +1,9 @@
package com.pj.test;
import cn.dev33.satoken.annotation.*;
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;
@@ -71,7 +66,7 @@ public class AtController {
}
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
@SaCheckBasic(account = "sa:123456")
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
+1 -1
View File
@@ -27,7 +27,7 @@
<!--<spring.version>4.2.5.RELEASE</spring.version>-->
<spring.version>5.3.7</spring.version>
<jackson.version>2.16.1</jackson.version>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -65,7 +65,7 @@ public class AtController {
}
// 通过Basic认证后才可以进入 ---- http://localhost:8080/sa_token_demo_ssm_war/at/checkBasic
@SaCheckBasic(account = "sa:123456")
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
<solon.version>2.7.0</solon.version>
</properties>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.38.0</sa-token.version>
<sa-token.version>1.39.0</sa-token.version>
</properties>
<dependencies>

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