Compare commits

..

103 Commits

Author SHA1 Message Date
click33
7652a51592 新增sso client 登录记录功能 2024-05-01 03:37:18 +08:00
click33
08659f1fa8 整体重构 sa-token-sso 模块,将 server 端和 client 端代码拆分 2024-04-30 08:34:30 +08:00
click33
32157cc389 调整友联 2024-04-28 15:15:31 +08:00
click33
e4af180ab8 将部分加密算法设置为过期 2024-04-28 03:05:50 +08:00
click33
4f26427bd5 SaSsoConfig 配置项 ssoLogoutCall 重命名为 currSsoLogoutCall 2024-04-27 18:20:57 +08:00
click33
f90bd2cd6a SaSsoConfig 新增配置 currSsoLogin,用于强制指定当前系统的 sso 登录地址 2024-04-27 18:13:16 +08:00
click33
bd313849fc 将 SaSsoConfig 中不必要的 get set 标注为过期 2024-04-27 17:36:00 +08:00
click33
21a4f66595 在 sa-token.sso.is-check-sign=false 时,控制台将输出醒目的警告信息 2024-04-27 16:57:18 +08:00
click33
2c87e47b2b SaSsoConfig 新增配置 isCheckSign(是否校验参数签名),方便本地开发时的调试。 2024-04-27 06:48:03 +08:00
click33
c8a5b922a2 API 调用签名校验时,限定参与签名的参数列表,更安全 2024-04-27 04:52:15 +08:00
click33
2db7ab6f4b SaSignTemplate#checkRequest 增加“可指定参与签名参数”的功能 2024-04-27 04:50:42 +08:00
click33
7b45385ffd 移除 SaSignConfig 的 isCheckNonce 配置项 2024-04-27 02:55:21 +08:00
click33
50f9e00576 优化文档 2024-04-27 02:18:59 +08:00
click33
a180330215 模式三校验 ticket 增加签名校验 2024-04-27 02:07:18 +08:00
click33
c8bcfa19d6 完善文档问答 2024-04-27 01:59:43 +08:00
click33
200f9fe642 修复“当登录时指定 timeout 小于全局 timeout 时,Account-Session 有效期为全局 timeout”的问题。
优化 首次获取 Token-Session 时,其有效期将保持和 token 有效期相同,而不是再是全局 timeout 值
2024-04-23 20:20:25 +08:00
click33
1277992b5d 增加部分注释 2024-04-23 20:20:03 +08:00
click33
ecb18f5b45 新增 StpUtil.getTokenLastActiveTime() 方法,获取当前 token 最后活跃时间。 2024-04-22 18:25:59 +08:00
click33
d238eea60d 新增 StpUtil.getLoginDeviceByToken(xxx) 方法,用于获取任意 token 的登录设备类型 2024-04-22 17:36:33 +08:00
click33
2a85f81dbc 优化文档 2024-04-22 17:34:41 +08:00
click33
46771bcb3b 优化:jwt模式 token 过期后,抛出的异常描述是 token 已过期,而不再是 token 无效 2024-04-22 02:37:49 +08:00
click33
5e354f3ae2 文档新增过滤器执行顺序更改示例 2024-04-21 18:58:24 +08:00
click33
5d733e4f4a 优化文档 2024-04-21 18:58:00 +08:00
click33
76e3f7ccc1 新增 issue 模板 2024-04-21 18:57:43 +08:00
click33
56101f8526 增加 SaHttpDigestTemplate 自动注入 2024-04-19 13:16:18 +08:00
click33
eaf43d0e5f 更换 HttpBasic 认证模块包名 2024-04-18 17:55:04 +08:00
click33
543613b5dd 新增 Http Digest 认证模块简单实现 2024-04-18 14:39:12 +08:00
click33
ccb79f6494 SSM 架构整合 Sa-Token 简单示例 2024-04-15 20:55:02 +08:00
click33
18bce0877e 扩大 SaTokenDaoForHutoolTimedCache 中 timedCache 可访问范围 2024-04-14 10:24:54 +08:00
click33
3ff0bbdeaf 新增插件:sa-token-hutool-timed-cache,用于整合 Hutool 缓存插件 TimedCache 2024-04-13 16:39:17 +08:00
click33
03e3925c4d 新增集成 beetl demo 示例 . 2024-04-13 11:08:36 +08:00
click33
f764f33b5d 新增集成 beetl demo 示例 2024-04-13 11:07:07 +08:00
click33
7f6b495578 完善 thymeleaf demo 测试页 2024-04-13 11:05:05 +08:00
click33
f2d3222b5d 解决 springboot 集成 sa-token 后排除 jsckson 依赖无法成功启动的问题 2024-04-13 07:04:47 +08:00
click33
7ad13a1c9d 将 Autowired 更换为更合适的 PostConstruct 2024-04-12 12:46:21 +08:00
click33
579aee164a StpUtil.getSessionBySessionId 提供的 SessionId 为空时将直接抛出异常,而不是再返回null 2024-04-12 10:29:21 +08:00
click33
217721cae6 重构 SaTokenException 类方法 throwBy->notTrue、throwByNull->notEmpty 2024-04-12 10:05:56 +08:00
click33
8a2a4f0372 保留过期类 InvalidContextException 向下兼容 2024-04-12 09:31:09 +08:00
click33
45ecb14c72 InvalidContextException 更名为 SaTokenContextException 2024-04-12 09:30:29 +08:00
click33
4a2751cc4b 调整示例代码 2024-04-12 09:27:31 +08:00
click33
b896daf9c7 更新 Dromara 项目清单 2024-04-12 02:48:57 +08:00
click33
8b815a6621 修正代码链接 2024-04-11 07:59:33 +08:00
click33
003e844254 移除不必要的配置项 2024-04-11 07:53:06 +08:00
click33
f5dc8b11bf 修复文档部分页面加粗文字不能正常显示的问题 2024-04-11 07:44:28 +08:00
click33
801e29632f 优化权限校验算法 2024-04-11 02:27:33 +08:00
click33
07e36f52c5 Merge pull request #584 from xiaotian1339/dev
集成thymeleaf插件移除渲染后的<html>标签的命名空间
2024-04-10 20:42:22 +08:00
click33
6de66bb9e6 更新赞助者名单 2024-04-10 14:52:05 +08:00
click33
0bd89831ab Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2024-04-05 03:19:58 +08:00
click33
b47ef019aa 优化文档 2024-04-05 03:18:44 +08:00
click33
4069dbc1f3 Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2024-04-05 03:16:28 +08:00
click33
be161174aa Merge pull request #552 from zhanglolo/patch-1
Update not-login-scene.md
2024-04-05 03:16:13 +08:00
click33
0679036d7b 优化文档 2024-04-05 03:01:29 +08:00
click33
60d88bc5e5 Merge pull request #544 from dromara/doc-thymeleaf-temp
Update thymeleaf-extend.md
2024-04-02 11:15:14 +08:00
刘潇
da0622d30c !292 优化 解决jwt重复设置 keyt 秘钥问题。
Merge pull request !292 from KonBAI/dev
2024-04-01 22:55:20 +00:00
click33
3f59792487 修复错误代码 2024-04-01 06:19:37 +08:00
click33
cb15d79029 优化版本检查器校验不通过时的提示语. 2024-04-01 06:19:23 +08:00
click33
e2603295c7 优化版本检查器校验不通过时的提示语 2024-04-01 06:18:41 +08:00
刘潇
3ce3160f05 !289 feat: Spring Boot3.x错误地使用sa-token-*spring-boot-starter时,将阻断程序启动
Merge pull request !289 from Uncarbon/dev
2024-03-30 20:18:53 +00:00
刘潇
0fb7b86f24 !293 solon 框架升为 2.7.0
Merge pull request !293 from 西东/dev
2024-03-30 20:04:08 +00:00
刘潇
9ae45da4a8 !290 update sa-token-doc/up/token-prefix.md.
Merge pull request !290 from zhengchalei/N/A
2024-03-30 20:03:35 +00:00
click33
b86fc06030 修复 StpUtil.getSessionByLoginId(xx) 参数为 null 时创建无效 SaSession 的 bug。 2024-03-27 18:21:37 +08:00
click33
dea944a141 跨域示例分离为单独项目 2024-03-27 16:40:01 +08:00
click33
413fce53ab test 2024-03-27 00:21:16 +08:00
click33
3b18d90f7f 新增公司案例 2024-03-24 15:21:06 +08:00
click33
0c4e3d6741 更新案例列表 2024-03-24 14:40:36 +08:00
click33
6777953f17 更新赞助清单 2024-03-20 14:29:37 +08:00
click33
45965f6b21 更新赞助者名单 2 2024-03-20 11:10:04 +08:00
click33
3070e06ffc 更新赞助者名单 2024-03-20 09:47:58 +08:00
click33
bd08d840d8 文档赞助页增加分页能力 2024-03-19 17:08:34 +08:00
click33
03bcf61fb5 官网首页新增赞助者名单 2024-03-19 16:15:14 +08:00
click33
dbf3cff2bb 优化官网 2024-03-18 10:48:02 +08:00
noear
786928f5e5 SaTokenInterceptor 增加 SaFilter 接口实现(代码没动,原先就支持)。保持与 SaTokenFilter 相同的体验。 2024-03-03 11:38:07 +08:00
noear
636b1bc874 SaTokenFilter 增加 getIncludeList,getExcludeList 方法(SaTokenInterceptor 有,所以也加下) 2024-03-03 11:37:08 +08:00
noear
075e22650b solon 框架升为 2.7.0 2024-03-03 11:13:48 +08:00
xiaotian1339
3d1deba562 移除<html>标签命名空间 2024-02-09 18:21:05 +08:00
KonBAI
275a68a82f 优化 解决jwt重复设置 keyt 秘钥问题。 2024-02-05 17:06:17 +08:00
zhengchalei
9087aea6dd update sa-token-doc/up/token-prefix.md.
修正properties格式错误

Signed-off-by: zhengchalei <stone981023@gmail.com>
2023-12-28 18:15:16 +00:00
click33
562c369cf2 更换qq群链接 2023-12-07 15:36:26 +08:00
click33
87c4dfefbd 增加公司信息 2023-12-01 17:36:59 +08:00
Mr.zhang
d4d213806b Update not-login-scene.md
关联token过期相关资料
2023-11-29 22:11:01 +08:00
ly-chn
ee16e919fe Update thymeleaf-extend.md
有人反馈在springboot3.x版本中, 在SaTokenConfigure类注入stp, 会导致循环依赖(根据文档来搭建环境, 大部分人的SaTokenConfigure类都继承了WebMvcConfigurer)
2023-11-13 17:14:39 +08:00
Uncarbon
49e51a2114 pref: 简化SpringBootVersionCompatibilityChecker代码 2023-11-10 14:21:40 +08:00
Uncarbon
8f75dd067c feat: Spring Boot3.x错误地使用sa-token-*spring-boot-starter时,将阻断程序启动 2023-11-10 14:15:21 +08:00
click33
670cd4ae98 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-10-31 07:47:21 +08:00
click33
4f84818100 同步赞助名单和项目友联 2023-10-31 07:47:06 +08:00
刘潇
9398f07cb5 !287 redisx 框架升为 1.6.0
Merge pull request !287 from 西东/dev
2023-10-30 22:25:56 +00:00
noear
8826565f6a redisx 框架升为 1.6.0 2023-10-18 18:18:15 +08:00
click33
e076c71648 v1.37.0 update 2023-10-18 13:22:36 +08:00
click33
f5307a2eb0 提供SpringBoot3.x路由匹配出错的解决方案 2023-10-18 02:26:35 +08:00
click33
da22832ccf 优化包结构 2023-10-18 02:26:12 +08:00
click33
6977d3d136 优化 SaTokenDao 组件更换时的逻辑 2023-10-17 12:14:42 +08:00
click33
0f7e1e977c Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2023-10-17 11:42:19 +08:00
click33
e918f613b8 未登录时调用鉴权 API 抛出未登录异常而不再是无权限异常。 2023-10-17 11:42:06 +08:00
刘潇
106a710dd8 !286 SaTokenDao 新增默认方法 onChange
Merge pull request !286 from moon69/dev
2023-10-17 03:40:37 +00:00
刘潇
4607f51d1a !284 解决jfinal5开启兼容json请求解析功能后报类转换异常问题
Merge pull request !284 from yangghosts/N/A
2023-10-17 03:25:27 +00:00
click33
954efeb732 修复路由拦截鉴权可被绕过的问题 fix #515 2023-10-16 16:02:19 +08:00
moon69
8db78e0a5d SaTokenDao 新增默认方法 onChange,当 SaManager.saTokenDao 变更时,调用该方法 2023-10-14 12:36:00 +08:00
yangghosts
e5fb308628 解决jfinal5开启兼容json请求解析功能后报类转换异常问题
解决jfinal5开启兼容json请求解析功能后报类转换异常问题。
       //JFinal新版本已经支持直接解析contentType=application/json请求的rawdata数据了
       constants.setResolveJsonRequest(true);

java.lang.ClassCastException: io.undertow.servlet.spec.HttpServletRequestImpl cannot be cast to com.jfinal.core.paragetter.JsonRequest
	at com.jfinal.core.paragetter.ModelGetter.get(ModelGetter.java:41)
	at com.jfinal.core.paragetter.ParaProcessor.get(ParaProcessor.java:51)
	at com.jfinal.aop.Invocation.<init>(Invocation.java:86)
	at cn.dev33.satoken.jfinal.SaTokenActionHandler.handle(SaTokenActionHandler.java:82)
	at com.jfinal.ext.handler.ContextPathHandler.handle(ContextPathHandler.java:48)
	at com.jfinal.core.JFinalFilter.doFilter(JFinalFilter.java:90)
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67)
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)

Signed-off-by: yangghosts <yangghosts@163.com>
2023-10-09 14:34:32 +00:00
click33
f2416a6175 修复小图标不能正常显示的问题 2023-09-23 06:25:26 +08:00
click33
591ad6ac53 修复小图标不能正常显示 2023-09-22 14:14:08 +08:00
click33
c0587d5e92 Merge branch 'dev' of https://github.com/dromara/Sa-Token into dev 2023-09-22 03:51:21 +08:00
click33
33ece1a2b0 v1.36.0 update. 2023-09-22 03:50:55 +08:00
click33
1fcda76ce4 v1.36.0 update 2023-09-22 03:42:41 +08:00
302 changed files with 9073 additions and 6099 deletions

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.36.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.37.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>
@@ -184,7 +184,7 @@ Sa-OAuth2 模块分为四种授权模式,解决不同场景下的授权需求
## 交流群
QQ交流群837325627 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=98NOhX0Q3a2hcv3eURcnYMBuZUZrlHUH&authKey=td3pmX3BnYNr%2FCRkEDwE5FgGARk29D9HAMwL0bAfK7tqN8XN93jccnEanyZl18mM&noverify=0&group_code=837325627)
QQ交流群685792424 [点击加入](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Y05Ld4125W92YSwZ0gA8e3RhG9Q4Vsfx&authKey=IomXuIuhP9g8G7l%2ByfkrRsS7i%2Fna0lIBpkTXxx%2BQEaz0NNEyJq00kgeiC4dUyNLS&noverify=0&group_code=685792424)
微信交流群:

View File

@@ -18,12 +18,17 @@ cd sa-token-demo-springboot-redis & call mvn clean & cd ..
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
cd sa-token-demo-test & call mvn clean & cd ..
cd sa-token-demo-thymeleaf & call mvn clean & cd ..
cd sa-token-demo-beetl & call mvn clean & cd ..
cd sa-token-demo-webflux & call mvn clean & cd ..
cd sa-token-demo-webflux-springboot3 & call mvn clean & cd ..
cd sa-token-demo-websocket & call mvn clean & cd ..
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
cd sa-token-demo-bom-import & call mvn clean & cd ..
cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
cd sa-token-demo-sso
cd sa-token-demo-sso-server & call mvn clean & cd ..
cd sa-token-demo-sso1-client & call mvn clean & cd ..
@@ -44,10 +49,10 @@ cd sa-token-demo-oauth2-client & call mvn clean & cd ..
cd sa-token-demo-oauth2-server & call mvn clean & cd ..
cd ..
cd sa-token-demo-cross
cd sa-token-demo-cross-header-server & call mvn clean & cd ..
cd sa-token-demo-cross-cookie-server & call mvn clean & cd ..
cd ..
:: cd sa-token-demo-cross
:: cd sa-token-demo-cross-header-server & call mvn clean & cd ..
:: cd sa-token-demo-cross-cookie-server & call mvn clean & cd ..
:: cd ..
cd sa-token-demo-dubbo
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..

View File

@@ -37,7 +37,7 @@
<!-- 一些属性 -->
<properties>
<revision>1.36.0</revision>
<revision>1.37.0</revision>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>

View File

@@ -13,7 +13,7 @@
<url>https://github.com/dromara/sa-token</url>
<properties>
<revision>1.36.0</revision>
<revision>1.37.0</revision>
</properties>
<dependencyManagement>

View File

@@ -102,10 +102,13 @@ public class SaManager {
SaTokenEventCenter.doRegisterComponent("SaTokenDao", saTokenDao);
}
private static void setSaTokenDaoMethod(SaTokenDao saTokenDao) {
if((SaManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaManager.saTokenDao).endRefreshThread();
if (SaManager.saTokenDao != null) {
SaManager.saTokenDao.destroy();
}
SaManager.saTokenDao = saTokenDao;
if (SaManager.saTokenDao != null) {
SaManager.saTokenDao.init();
}
}
public static SaTokenDao getSaTokenDao() {
if (saTokenDao == null) {

View File

@@ -15,7 +15,7 @@
*/
package cn.dev33.satoken.annotation;
import cn.dev33.satoken.basic.SaBasicTemplate;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicTemplate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -32,13 +32,13 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaCheckBasic {
public @interface SaCheckHttpBasic {
/**
* 领域
* @return /
*/
String realm() default SaBasicTemplate.DEFAULT_REALM;
String realm() default SaHttpBasicTemplate.DEFAULT_REALM;
/**
* 需要校验的账号密码格式形如 sa:123456

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.annotation;
import cn.dev33.satoken.httpauth.digest.SaHttpDigestModel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Http Digest 认证校验:只有通过 Http Digest 认证后才能进入该方法,否则抛出异常。
*
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
*
* @author click33
* @since 1.38.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaCheckHttpDigest {
/**
* 用户名
* @return /
*/
String username() default "";
/**
* 密码
* @return /
*/
String password() default "";
/**
* 领域
* @return /
*/
String realm() default SaHttpDigestModel.DEFAULT_REALM;
/**
* 需要校验的用户名和密码,格式形如 sa:123456
* @return /
*/
String value() default "";
}

View File

@@ -61,11 +61,11 @@ public @interface SaCheckOr {
SaCheckSafe[] safe() default {};
/**
* 设定 @SaCheckBasic参考 {@link SaCheckBasic}
* 设定 @SaCheckBasic参考 {@link SaCheckHttpBasic}
*
* @return /
*/
SaCheckBasic[] basic() default {};
SaCheckHttpBasic[] basic() default {};
/**
* 设定 @SaCheckDisable参考 {@link SaCheckDisable}

View File

@@ -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.application;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 应用全局信息
*
* @author click33
* @since 1.31.0
*/
public class ApplicationInfo {
/**
* 应用前缀
*/
public static String routePrefix;
/**
* 为指定 path 裁剪掉 routePrefix 前缀
* @param path 指定 path
* @return /
*/
public static String cutPathPrefix(String path) {
if(! SaFoxUtil.isEmpty(routePrefix) && ! routePrefix.equals("/") && path.startsWith(routePrefix)){
path = path.substring(routePrefix.length());
}
return path;
}
}

View File

@@ -23,11 +23,15 @@ 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 {
/**

View File

@@ -16,11 +16,15 @@
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() {

View File

@@ -36,11 +36,6 @@ public class SaSignConfig {
*/
private long timestampDisparity = 1000 * 60 * 15;
/**
* 是否校验 nonce 随机字符串
*/
private Boolean isCheckNonce = true;
/**
* 获取 API 调用签名秘钥
@@ -88,26 +83,6 @@ public class SaSignConfig {
return this;
}
/**
* 获取 是否校验 nonce 随机字符串
*
* @return /
*/
public Boolean getIsCheckNonce() {
return this.isCheckNonce;
}
/**
* 设置 是否校验 nonce 随机字符串
*
* @param isCheckNonce /
* @return 对象自身
*/
public SaSignConfig setIsCheckNonce(Boolean isCheckNonce) {
this.isCheckNonce = isCheckNonce;
return this;
}
/**
* 计算保存 nonce 时应该使用的 ttl单位
* @return /
@@ -128,8 +103,18 @@ public class SaSignConfig {
return "SaSignConfig ["
+ "secretKey=" + secretKey
+ ", timestampDisparity=" + timestampDisparity
+ ", isCheckNonce=" + isCheckNonce
+ "]";
}
/**
* 设置:是否校验 nonce 随机字符串
* <h2> isCheckNonce 方案已废弃,不再提供此配置项 </h2>
*
* @param isCheckNonce /
*/
@Deprecated
public void setIsCheckNonce(Boolean isCheckNonce) {
System.err.println("--------- isCheckNonce 方案已废弃,不再提供此配置项 ---------");
}
}

View File

@@ -15,7 +15,6 @@
*/
package cn.dev33.satoken.config;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
@@ -148,9 +147,14 @@ public class SaTokenConfig implements Serializable {
private String jwtSecretKey;
/**
* Http Basic 认证的默认账号和密码
* Http Basic 认证的默认账号和密码冒号隔开例如sa:123456
*/
private String basic = "";
private String httpBasic = "";
/**
* Http Digest 认证的默认账号和密码冒号隔开例如sa:123456
*/
private String httpDigest = "";
/**
* 配置当前项目的网络访问地址
@@ -555,18 +559,34 @@ public class SaTokenConfig implements Serializable {
}
/**
* @return Http Basic 认证的默认账号和密码
* @return Http Basic 认证的默认账号和密码冒号隔开例如sa:123456
*/
public String getBasic() {
return basic;
public String getHttpBasic() {
return httpBasic;
}
/**
* @param basic Http Basic 认证的默认账号和密码
* @param httpBasic Http Basic 认证的默认账号和密码冒号隔开例如sa:123456
* @return 对象自身
*/
public SaTokenConfig setBasic(String basic) {
this.basic = basic;
public SaTokenConfig setHttpBasic(String httpBasic) {
this.httpBasic = httpBasic;
return this;
}
/**
* @return Http Digest 认证的默认账号和密码冒号隔开例如sa:123456
*/
public String getHttpDigest() {
return httpDigest;
}
/**
* @param httpDigest Http Digest 认证的默认账号和密码冒号隔开例如sa:123456
* @return 对象自身
*/
public SaTokenConfig setHttpDigest(String httpDigest) {
this.httpDigest = httpDigest;
return this;
}
@@ -676,7 +696,8 @@ public class SaTokenConfig implements Serializable {
+ ", logLevelInt=" + logLevelInt
+ ", isColorLog=" + isColorLog
+ ", jwtSecretKey=" + jwtSecretKey
+ ", basic=" + basic
+ ", httpBasic=" + httpBasic
+ ", httpDigest=" + httpDigest
+ ", currDomain=" + currDomain
+ ", sameTokenTimeout=" + sameTokenTimeout
+ ", checkSameToken=" + checkSameToken
@@ -685,8 +706,12 @@ public class SaTokenConfig implements Serializable {
+ "]";
}
// ------------------- 过期方法 -------------------
/**
* 请更改为 getActiveTimeout()
* <h2> 请更改为 getActiveTimeout() </h2>
* @return token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
* (例如可以设置为 1800 代表 30 分钟内无操作就冻结)
*/
@@ -697,7 +722,7 @@ public class SaTokenConfig implements Serializable {
}
/**
* 请更改为 setActiveTimeout()
* <h2> 请更改为 setActiveTimeout() </h2>
* @param activityTimeout token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
* (例如可以设置为 1800 代表 30 分钟内无操作就冻结)
* @return 对象自身
@@ -709,5 +734,24 @@ public class SaTokenConfig implements Serializable {
return this;
}
/**
* <h2> 请更改为 getHttpBasic() </h2>
* @return Http Basic 认证的默认账号和密码
*/
@Deprecated
public String getBasic() {
return httpBasic;
}
/**
* <h2> 请更改为 setHttpBasic() </h2>
* @param basic Http Basic 认证的默认账号和密码
* @return 对象自身
*/
@Deprecated
public SaTokenConfig setBasic(String basic) {
this.httpBasic = basic;
return this;
}
}

View File

@@ -19,7 +19,7 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.InvalidContextException;
import cn.dev33.satoken.exception.SaTokenContextException;
/**
* Sa-Token 上下文处理器 [ 默认实现类 ]
@@ -46,22 +46,22 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
@Override
public SaRequest getRequest() {
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public SaResponse getResponse() {
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public SaStorage getStorage() {
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
@Override
public boolean matchPath(String pattern, String path) {
throw new InvalidContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
}
}

View File

@@ -19,7 +19,7 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.InvalidContextException;
import cn.dev33.satoken.exception.SaTokenContextException;
/**
* Sa-Token 上下文处理器 [ThreadLocal 版本] ---- 对象存储器
@@ -69,7 +69,7 @@ public class SaTokenContextForThreadLocalStorage {
public static Box getBoxNotNull() {
Box box = boxThreadLocal.get();
if(box == null) {
throw new InvalidContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
throw new SaTokenContextException("未能获取有效的上下文").setCode(SaErrorCode.CODE_10002);
}
return box;
}

View File

@@ -199,6 +199,20 @@ public interface SaTokenDao {
* @return 查询到的数据集合
*/
List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType);
// --------------------- 生命周期 ---------------------
/**
* 当此 SaTokenDao 实例被装载时触发
*/
default void init() {
}
/**
* 当此 SaTokenDao 实例被卸载时触发
*/
default void destroy() {
}
}

View File

@@ -40,14 +40,6 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
* 存储数据过期时间的集合(单位: 毫秒), 记录所有 key 的到期时间 (注意存储的是到期时间,不是剩余存活时间)
*/
public Map<String, Long> expireMap = new ConcurrentHashMap<>();
/**
* 构造函数
*/
public SaTokenDaoDefaultImpl() {
initRefreshThread();
}
// ------------------------ String 读写操作
@@ -260,12 +252,21 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
});
this.refreshThread.start();
}
/**
* 结束定时任务,不再定时清理过期数据
* 组件被安装时,开始刷新数据线程
*/
public void endRefreshThread() {
this.refreshFlag = false;
@Override
public void init() {
initRefreshThread();
}
/**
* 组件被卸载时,结束定时任务,不再定时清理过期数据
*/
@Override
public void destroy() {
this.refreshFlag = false;
}
}

View File

@@ -60,6 +60,9 @@ public interface SaErrorCode {
/** 表示未能通过 Http Basic 认证校验 */
int CODE_10311 = 10311;
/** 表示未能通过 Http Digest 认证校验 */
int CODE_10312 = 10312;
/** 提供的 HttpMethod 是无效的 */
int CODE_10321 = 10321;
@@ -118,7 +121,10 @@ public interface SaErrorCode {
/** 二级认证校验未通过 */
int CODE_11071 = 11071;
/** 获取 SaSession 时提供的 SessionId 为空 */
int CODE_11072 = 11072;
// ------------

View File

@@ -17,10 +17,12 @@ package cn.dev33.satoken.exception;
/**
* 一个异常:代表框架未能获取有效的上下文
* <h1>已过期:请更名为 SaTokenContextException 用法不变,未来版本将彻底删除此类</h1>
*
* @author click33
* @since 1.33.0
*/
@Deprecated
public class InvalidContextException extends SaTokenException {
/**
@@ -30,7 +32,7 @@ public class InvalidContextException extends SaTokenException {
/**
* 一个异常:代表框架未能获取有效的上下文
* @param message 异常描述
* @param message 异常描述
*/
public InvalidContextException(String message) {
super(message);

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表会话未能通过 Http Digest 认证校验
*
* @author click33
* @since 1.38.0
*/
public class NotHttpDigestAuthException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/** 异常提示语 */
public static final String BE_MESSAGE = "no http digest auth";
/**
* 一个异常:代表会话未通过 Http Digest 认证
*/
public NotHttpDigestAuthException() {
super(BE_MESSAGE);
}
}

View File

@@ -64,13 +64,4 @@ public class NotPermissionException extends SaTokenException {
this.loginType = loginType;
}
/**
* <h1> 警告:自 v1.30+ 版本起,获取异常权限码由 getCode() 更改为 getPermission(),请及时更换! </h1>
* @return 获得权限码
*/
@Deprecated
public int getCode() {
return super.getCode();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
/**
* 一个异常:代表请求 path 无效或非法
*
* @author click33
* @since 1.37.0
*/
public class RequestPathInvalidException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 8243974276159004739L;
/** 具体无效的 path */
private final String path;
/**
* @return 具体无效的 path
*/
public String getPath() {
return path;
}
public RequestPathInvalidException(String message, String path) {
super(message);
this.path = path;
}
}

View File

@@ -38,11 +38,40 @@ public class SaSignException extends SaTokenException {
super(message);
}
/**
* 断言 flag 不为 true否则抛出 message 异常
* @param flag 表达式
* @param message 异常信息
*/
public static void notTrue(boolean flag, String message) {
// notTrue
if(flag) {
throw new SaSignException(message);
}
}
/**
* 断言 value 不为空,否则抛出 message 异常
* @param value 值
* @param message 异常信息
*/
public static void notEmpty(Object value, String message) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaSignException(message);
}
}
// ------------------- 已过期 -------------------
/**
* 如果flag==true则抛出message异常
* <h2>已过期:请使用 notTrue 代替,用法不变</h2>
*
* @param flag 标记
* @param message 异常信息
*/
@Deprecated
public static void throwBy(boolean flag, String message) {
if(flag) {
throw new SaSignException(message);
@@ -51,13 +80,17 @@ public class SaSignException extends SaTokenException {
/**
* 如果 value isEmpty则抛出 message 异常
* <h2>已过期:请使用 notEmpty 代替,用法不变</h2>
*
* @param value 值
* @param message 异常信息
*/
@Deprecated
public static void throwByNull(Object value, String message) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaSignException(message);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.exception;
import java.io.Serializable;
/**
* 一个异常:代表框架未能获取有效的上下文
*
* @author click33
* @since 1.33.0
*/
public class SaTokenContextException extends InvalidContextException implements Serializable {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130144L;
/**
* 一个异常:代表框架未能获取有效的上下文
* @param message 异常描述
*/
public SaTokenContextException(String message) {
super(message);
}
}

View File

@@ -105,13 +105,60 @@ public class SaTokenException extends RuntimeException {
this.code = code;
return this;
}
/**
* 如果flag==true则抛出message异常
* 断言 flag 不为 true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
*/
public static void notTrue(boolean flag, String message) {
notTrue(flag, message, SaErrorCode.CODE_UNDEFINED);
}
/**
* 断言 flag 不为 true否则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分状态码
*/
public static void notTrue(boolean flag, String message, int code) {
if(flag) {
throw new SaTokenException(message).setCode(code);
}
}
/**
* 断言 value 不为空,否则抛出 message 异常
* @param value 值
* @param message 异常信息
*/
public static void notEmpty(Object value, String message) {
notEmpty(value, message, SaErrorCode.CODE_UNDEFINED);
}
/**
* 断言 value 不为空,否则抛出 message 异常
* @param value 值
* @param message 异常信息
* @param code 异常细分状态码
*/
public static void notEmpty(Object value, String message, int code) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaTokenException(message).setCode(code);
}
}
// ------------------- 已过期 -------------------
/**
* 如果flag==true则抛出message异常
* <h2>已过期:请使用 notTrue 代替,用法不变</h2>
*
* @param flag 标记
* @param message 异常信息
* @param code 异常细分状态码
*/
@Deprecated
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaTokenException(message).setCode(code);
@@ -119,15 +166,18 @@ public class SaTokenException extends RuntimeException {
}
/**
* 如果value==null或者isEmpty则抛出message异常
* @param value 值
* @param message 异常信息
* @param code 异常细分状态码
* 如果value==null或者isEmpty则抛出message异常
* <h2>已过期:请使用 notEmpty 代替,用法不变</h2>
*
* @param value 值
* @param message 异常信息
* @param code 异常细分状态码
*/
@Deprecated
public static void throwByNull(Object value, String message, int code) {
if(SaFoxUtil.isEmpty(value)) {
throw new SaTokenException(message).setCode(code);
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.fun.strategy;
import cn.dev33.satoken.exception.RequestPathInvalidException;
/**
* 函数式接口:校验请求 path 的算法
*
* <p> 如果属于无效请求 path则抛出异常 RequestPathInvalidException </p>
*
* @author click33
* @since 1.37.0
*/
@FunctionalInterface
public interface SaCheckRequestPathFunction {
/**
* 执行函数
* @param path 请求 path
* @param extArg1 扩展参数1
* @param extArg2 扩展参数2
*/
void run(String path, Object extArg1, Object extArg2);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.fun.strategy;
import cn.dev33.satoken.exception.RequestPathInvalidException;
/**
* 函数式接口:当请求 path 校验不通过时处理方案的算法
*
* @author click33
* @since 1.37.0
*/
@FunctionalInterface
public interface SaRequestPathInvalidHandleFunction {
/**
* 执行函数
* @param e 请求 path 无效的异常对象
* @param extArg1 扩展参数1
* @param extArg2 扩展参数2
*/
void run(RequestPathInvalidException e, Object extArg1, Object extArg2);
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.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.secure.SaBase64Util;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Sa-Token Http Basic 认证模块
*
* @author click33
* @since 1.26.0
*/
public class SaHttpBasicTemplate {
/**
* 默认的 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().getHttpBasic());
}
/**
* 对当前会话进行 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().getHttpBasic();
}
String authorization = getAuthorizationValue();
if(SaFoxUtil.isEmpty(authorization) || ! authorization.equals(account)) {
throwNotBasicAuthException(realm);
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.httpauth.basic;
/**
* Sa-Token Http Basic 认证模块Util 工具类
*
* @author click33
* @since 1.26.0
*/
public class SaHttpBasicUtil {
private SaHttpBasicUtil() {
}
/**
* 底层使用的 SaBasicTemplate 对象
*/
public static SaHttpBasicTemplate saHttpBasicTemplate = new SaHttpBasicTemplate();
/**
* 获取浏览器提交的 Basic 参数 (裁剪掉前缀并解码)
* @return 值
*/
public static String getAuthorizationValue() {
return saHttpBasicTemplate.getAuthorizationValue();
}
/**
* 对当前会话进行 Basic 校验(使用全局配置的账号密码),校验不通过则抛出异常
*/
public static void check() {
saHttpBasicTemplate.check();
}
/**
* 对当前会话进行 Basic 校验(手动设置账号密码),校验不通过则抛出异常
* @param account 账号(格式为 user:password
*/
public static void check(String account) {
saHttpBasicTemplate.check(account);
}
/**
* 对当前会话进行 Basic 校验(手动设置 Realm 和 账号密码),校验不通过则抛出异常
* @param realm 领域
* @param account 账号(格式为 user:password
*/
public static void check(String realm, String account) {
saHttpBasicTemplate.check(realm, account);
}
}

View File

@@ -0,0 +1,346 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.httpauth.digest;
/**
* Sa-Token Http Digest 认证 - 参数实体类
*
* @author click33
* @since 1.38.0
*/
public class SaHttpDigestModel {
/**
* 默认的 Realm 领域名称
*/
public static final String DEFAULT_REALM = "Sa-Token";
/**
* 默认的 qop 值
*/
public static final String DEFAULT_QOP = "auth";
/**
* 用户名
*/
public String username;
/**
* 密码
*/
public String password;
/**
* 领域
*/
public String realm = DEFAULT_REALM;
/**
* 随机数
*/
public String nonce;
/**
* 请求 uri
*/
public String uri;
/**
* 请求方法
*/
public String method;
/**
* 保护质量auth=默认的auth-int=增加报文完整性检测),可以为空,但不推荐
*/
public String qop;
/**
* nonce计数器是一个16进制的数值表示同一nonce下客户端发送出请求的数量
*/
public String nc;
/**
* 客户端随机数,由客户端提供
*/
public String cnonce;
/**
* opaque
*/
public String opaque;
/**
* 请求摘要,最终计算的摘要结果
*/
public String response;
// ------------------- 构造函数 -------------------
public SaHttpDigestModel() {
}
public SaHttpDigestModel(String username, String password) {
this.username = username;
this.password = password;
}
public SaHttpDigestModel(String username, String password, String realm) {
this.username = username;
this.password = password;
this.realm = realm;
}
// ------------------- get/set -------------------
/**
* 获取 用户名
*
* @return username 用户名
*/
public String getUsername() {
return this.username;
}
/**
* 设置 用户名
*
* @param username 用户名
* @return /
*/
public SaHttpDigestModel setUsername(String username) {
this.username = username;
return this;
}
/**
* 获取 领域
*
* @return realm 领域
*/
public String getRealm() {
return this.realm;
}
/**
* 设置 领域
*
* @param realm 领域
* @return /
*/
public SaHttpDigestModel setRealm(String realm) {
this.realm = realm;
return this;
}
/**
* 获取 密码
*
* @return password 密码
*/
public String getPassword() {
return this.password;
}
/**
* 设置 密码
*
* @param password 密码
* @return /
*/
public SaHttpDigestModel setPassword(String password) {
this.password = password;
return this;
}
/**
* 获取 随机数
*
* @return nonce 随机数
*/
public String getNonce() {
return this.nonce;
}
/**
* 设置 随机数
*
* @param nonce 随机数
* @return /
*/
public SaHttpDigestModel setNonce(String nonce) {
this.nonce = nonce;
return this;
}
/**
* 获取 请求 uri
*
* @return uri 请求 uri
*/
public String getUri() {
return this.uri;
}
/**
* 设置 请求 uri
*
* @param uri 请求 uri
* @return /
*/
public SaHttpDigestModel setUri(String uri) {
this.uri = uri;
return this;
}
/**
* 获取 请求方法
*
* @return method 请求方法
*/
public String getMethod() {
return this.method;
}
/**
* 设置 请求方法
*
* @param method 请求方法
* @return /
*/
public SaHttpDigestModel setMethod(String method) {
this.method = method;
return this;
}
/**
* 获取 保护质量auth=默认的auth-int=增加报文完整性检测),可以为空,但不推荐
*
* @return qop 保护质量auth=默认的auth-int=增加报文完整性检测),可以为空,但不推荐
*/
public String getQop() {
return this.qop;
}
/**
* 设置 保护质量auth=默认的auth-int=增加报文完整性检测),可以为空,但不推荐
*
* @param qop 保护质量auth=默认的auth-int=增加报文完整性检测),可以为空,但不推荐
* @return /
*/
public SaHttpDigestModel setQop(String qop) {
this.qop = qop;
return this;
}
/**
* 获取 nonce计数器是一个16进制的数值表示同一nonce下客户端发送出请求的数量
*
* @return nc nonce计数器是一个16进制的数值表示同一nonce下客户端发送出请求的数量
*/
public String getNc() {
return this.nc;
}
/**
* 设置 nonce计数器是一个16进制的数值表示同一nonce下客户端发送出请求的数量
*
* @param nc nonce计数器是一个16进制的数值表示同一nonce下客户端发送出请求的数量
* @return /
*/
public SaHttpDigestModel setNc(String nc) {
this.nc = nc;
return this;
}
/**
* 获取 客户端随机数,由客户端提供
*
* @return cnonce 客户端随机数,由客户端提供
*/
public String getCnonce() {
return this.cnonce;
}
/**
* 设置 客户端随机数,由客户端提供
*
* @param cnonce 客户端随机数,由客户端提供
* @return /
*/
public SaHttpDigestModel setCnonce(String cnonce) {
this.cnonce = cnonce;
return this;
}
/**
* 获取 opaque
*
* @return opaque opaque
*/
public String getOpaque() {
return this.opaque;
}
/**
* 设置 opaque
*
* @param opaque opaque
* @return /
*/
public SaHttpDigestModel setOpaque(String opaque) {
this.opaque = opaque;
return this;
}
/**
* 获取 请求摘要,最终计算的摘要结果
*
* @return response 请求摘要,最终计算的摘要结果
*/
public String getResponse() {
return this.response;
}
/**
* 设置 请求摘要,最终计算的摘要结果
*
* @param response 请求摘要,最终计算的摘要结果
* @return /
*/
public SaHttpDigestModel setResponse(String response) {
this.response = response;
return this;
}
@Override
public String toString() {
return "SaHttpDigestModel[" +
"username=" + username +
", password=" + password +
", realm=" + realm +
", nonce=" + nonce +
", uri=" + uri +
", method=" + method +
", qop=" + qop +
", nc=" + nc +
", cnonce=" + cnonce +
", opaque=" + opaque +
", response=" + response +
"]";
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.httpauth.digest;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.error.SaErrorCode;
import cn.dev33.satoken.exception.NotHttpDigestAuthException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token Http Digest 认证模块 - 模板方法类
*
* @author click33
* @since 1.38.0
*/
public class SaHttpDigestTemplate {
/*
这里只是 Http Digest 认证的一个简单实现,待实现功能还有:
1、nonce 防重放攻击
2、nc 计数器
3、qop 保护质量=auth-int
4、opaque 透明值
5、algorithm 更多摘要算法
等等
*/
/**
* 构建认证失败的响应头参数
* @param model 参数对象
* @return 响应头值
*/
public String buildResponseHeaderValue(SaHttpDigestModel model) {
// 抛异常
String headerValue = "Digest " +
"realm=\"" + model.realm + "\", " +
"qop=\"" + model.qop + "\", " +
"nonce=\"" + model.nonce + "\", " +
"nc=" + model.nc + ", " +
"opaque=\"" + model.opaque + "\"";
return headerValue;
}
/**
* 在校验失败时,设置响应头,并抛出异常
* @param model Digest 参数对象
*/
public void throwNotHttpDigestAuthException(SaHttpDigestModel model) {
// 补全一些必须的参数
model.realm = (model.realm != null) ? model.realm : SaHttpDigestModel.DEFAULT_REALM;
model.qop = (model.qop != null) ? model.qop : SaHttpDigestModel.DEFAULT_QOP;
model.nonce = (model.nonce != null) ? model.nonce : SaFoxUtil.getRandomString(32);
model.opaque = (model.opaque != null) ? model.opaque : SaFoxUtil.getRandomString(32);
model.nc = (model.nc != null) ? model.nc : "00000001";
// 设置响应头
SaHolder.getResponse()
.setStatus(401)
.setHeader("WWW-Authenticate", buildResponseHeaderValue(model));
// 抛异常
throw new NotHttpDigestAuthException().setCode(SaErrorCode.CODE_10312);
}
/**
* 获取浏览器提交的 Digest 参数 (裁剪掉前缀)
* @return 值
*/
public String getAuthorizationValue() {
// 获取前端提交的请求头 Authorization 参数
String authorization = SaHolder.getRequest().getHeader("Authorization");
// 如果不是以 Digest 作为前缀,则视为无效
if(authorization == null || ! authorization.startsWith("Digest ")) {
return null;
}
// 裁剪前缀并解码
return authorization.substring(7);
}
/**
* 获取浏览器提交的 Digest 参数,并转化为 Map
* @return /
*/
public SaHttpDigestModel getAuthorizationValueToModel() {
// 先获取字符串值
String authorization = getAuthorizationValue();
if(authorization == null) {
// throw new SaTokenException("请求头中未携带 Digest 认证参数");
return null;
}
// 根据逗号分割,解析为 Map
Map<String, String> map = new LinkedHashMap<>();
String[] arr = authorization.split(",");
for (String s : arr) {
String[] kv = s.split("=");
if (kv.length == 2) {
map.put(kv[0].trim(), kv[1].trim().replace("\"", ""));
}
}
/*
参考样例:
username=sa,
realm=Sa-Token,
nonce=dcd98b7102dd2f0e8b11d0f600bfb0c093,
uri=/test/testDigest,
response=a32023c128e142163dd4856a2f511c70,
opaque=5ccc069c403ebaf9f0171e9517f40e41,
qop=auth,
nc=00000002,
cnonce=f3ca6bfc0b2f59c4
*/
// 转化为 Model
SaHttpDigestModel model = new SaHttpDigestModel();
model.username = map.get("username");
model.realm = map.get("realm");
model.nonce = map.get("nonce");
model.uri = map.get("uri");
model.method = SaHolder.getRequest().getMethod();
model.qop = map.get("qop");
model.nc = map.get("nc");
model.cnonce = map.get("cnonce");
model.opaque = map.get("opaque");
model.response = map.get("response");
//
return model;
}
/**
* 计算:根据 Digest 参数计算 response
*
* @param model Digest 参数对象
* @return 计算出的 response
*/
public String calcResponse(SaHttpDigestModel model) {
// frag1 = md5(username:realm:password)
String frag1 = SaSecureUtil.md5(model.username + ":" + model.realm + ":" + model.password);
// frag2 = nonce:nc:cnonce:qop
String frag2 = model.nonce + ":" + model.nc + ":" + model.cnonce + ":" + model.qop;
// frag3 = md5(method:uri)
String frag3 = SaSecureUtil.md5(model.method + ":" + model.uri);
// 最终结果 = md5(frag1:frag2:frag3)
String response = SaSecureUtil.md5(frag1 + ":" + frag2 + ":" + frag3);
//
return response;
}
/**
* 把 hopeModel 有的值都 copy 到 reqModel 中
*/
public void copyHopeToReq(SaHttpDigestModel hopeModel, SaHttpDigestModel reqModel){
reqModel.username = hopeModel.username;
reqModel.password = hopeModel.password;
reqModel.realm = hopeModel.realm != null ? hopeModel.realm : reqModel.realm;
reqModel.nonce = hopeModel.nonce != null ? hopeModel.nonce : reqModel.nonce;
reqModel.uri = hopeModel.uri != null ? hopeModel.uri : reqModel.uri;
reqModel.method = hopeModel.method != null ? hopeModel.method : reqModel.method;
reqModel.qop = hopeModel.qop != null ? hopeModel.qop : reqModel.qop;
reqModel.nc = hopeModel.nc != null ? hopeModel.nc : reqModel.nc;
reqModel.opaque = hopeModel.opaque != null ? hopeModel.opaque : reqModel.opaque;
// reqModel.cnonce = hopeModel.cnonce != null ? hopeModel.cnonce : reqModel.cnonce;
// reqModel.response = hopeModel.response != null ? hopeModel.response : reqModel.response;
}
// ---------- 校验 ----------
/**
* 校验:根据提供 Digest 参数计算 res与 request 请求中的 Digest 参数进行校验,校验不通过则抛出异常
* @param hopeModel 提供的 Digest 参数对象
*/
public void check(SaHttpDigestModel hopeModel) {
// 先进行一些必须的希望参数校验
SaTokenException.notEmpty(hopeModel, "Digest参数对象不能为空");
SaTokenException.notEmpty(hopeModel.username, "必须提供希望的 username 参数");
SaTokenException.notEmpty(hopeModel.password, "必须提供希望的 password 参数");
// 获取 web 请求中的 Digest 参数
SaHttpDigestModel reqModel = getAuthorizationValueToModel();
// 为空代表前端根本没有提交 Digest 参数,直接抛异常
if(reqModel == null) {
throwNotHttpDigestAuthException(hopeModel);
}
// 把 hopeModel 有的值都 copy 到 reqModel 中
copyHopeToReq(hopeModel, reqModel);
// 计算
String cResponse = calcResponse(reqModel);
// 比对,不一致就抛异常
if(! cResponse.equals(reqModel.response)) {
throwNotHttpDigestAuthException(hopeModel);
}
// 认证通过
}
/**
* 校验:根据提供的参数,校验不通过抛出异常
* @param username 用户名
* @param password 密码
*/
public void check(String username, String password) {
check(new SaHttpDigestModel(username, password));
}
/**
* 校验:根据提供的参数,校验不通过抛出异常
* @param username 用户名
* @param password 密码
* @param realm 领域
*/
public void check(String username, String password, String realm) {
check(new SaHttpDigestModel(username, password, realm));
}
/**
* 校验:根据全局配置参数,校验不通过抛出异常
*/
public void check() {
String httpDigest = SaManager.getConfig().getHttpDigest();
if(SaFoxUtil.isEmpty(httpDigest)){
throw new SaTokenException("未配置全局 Http Digest 认证参数");
}
String[] arr = httpDigest.split(":");
if(arr.length != 2){
throw new SaTokenException("全局 Http Digest 认证参数配置错误格式应如username:password");
}
check(arr[0], arr[1]);
}
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(SaCheckHttpDigest at) {
// 如果配置了 value则以 value 优先
String value = at.value();
if(SaFoxUtil.isNotEmpty(value)){
String[] arr = value.split(":");
if(arr.length != 2){
throw new SaTokenException("注解参数配置错误格式应如username:password");
}
check(arr[0], arr[1]);
return;
}
// 如果配置了 username则分别获取参数
String username = at.username();
if(SaFoxUtil.isNotEmpty(username)){
check(username, at.password(), at.realm());
return;
}
// 都没有配置,则根据全局配置参数进行校验
check();
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.httpauth.digest;
import cn.dev33.satoken.annotation.SaCheckHttpDigest;
import cn.dev33.satoken.context.SaHolder;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token Http Digest 认证模块Util 工具类
*
* @author click33
* @since 1.38.0
*/
public class SaHttpDigestUtil {
private SaHttpDigestUtil() {
}
/**
* 底层使用的 SaHttpDigestTemplate 对象
*/
public static SaHttpDigestTemplate saHttpDigestTemplate = new SaHttpDigestTemplate();
/**
* 获取浏览器提交的 Digest 参数 (裁剪掉前缀)
* @return 值
*/
public static String getAuthorizationValue() {
return saHttpDigestTemplate.getAuthorizationValue();
}
/**
* 获取浏览器提交的 Digest 参数,并转化为 Map
* @return /
*/
public static SaHttpDigestModel getAuthorizationValueToModel() {
return saHttpDigestTemplate.getAuthorizationValueToModel();
}
// ---------- 校验 ----------
/**
* 校验:根据提供 Digest 参数计算 res与 request 请求中的 Digest 参数进行校验,校验不通过则抛出异常
* @param hopeModel 提供的 Digest 参数对象
*/
public static void check(SaHttpDigestModel hopeModel) {
saHttpDigestTemplate.check(hopeModel);
}
/**
* 校验:根据提供的参数,校验不通过抛出异常
* @param username 用户名
* @param password 密码
*/
public static void check(String username, String password) {
saHttpDigestTemplate.check(username, password);
}
/**
* 校验:根据提供的参数,校验不通过抛出异常
* @param username 用户名
* @param password 密码
* @param realm 领域
*/
public static void check(String username, String password, String realm) {
saHttpDigestTemplate.check(username, password, realm);
}
/**
* 校验:根据全局配置参数,校验不通过抛出异常
*/
public static void check() {
saHttpDigestTemplate.check();
}
/**
* 根据注解 ( @SaCheckHttpDigest ) 鉴权
*
* @param at 注解对象
*/
public static void checkByAnnotation(SaCheckHttpDigest at) {
saHttpDigestTemplate.checkByAnnotation(at);
}
}

View File

@@ -175,6 +175,7 @@ public class SaSecureUtil {
* @param salt 盐
* @return 加密后的字符串
*/
@Deprecated
public static String md5BySalt(String str, String salt) {
return md5(md5(str) + md5(salt));
}
@@ -185,6 +186,7 @@ public class SaSecureUtil {
* @param salt 盐
* @return 加密后的字符串
*/
@Deprecated
public static String sha256BySalt(String str, String salt) {
return sha256(sha256(str) + sha256(salt));
}
@@ -261,6 +263,7 @@ public class SaSecureUtil {
* @return Map对象 (private=私钥, public=公钥)
* @throws Exception 异常
*/
@Deprecated
public static HashMap<String, String> rsaGenerateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
@@ -285,6 +288,7 @@ public class SaSecureUtil {
* @param content 内容
* @return 加密后内容
*/
@Deprecated
public static String rsaEncryptByPublic(String publicKeyString, String content) {
try {
// 获得公钥对象
@@ -311,6 +315,7 @@ public class SaSecureUtil {
* @param content 内容
* @return 加密后内容
*/
@Deprecated
public static String rsaEncryptByPrivate(String privateKeyString, String content) {
try {
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
@@ -336,6 +341,7 @@ public class SaSecureUtil {
* @param content 已加密内容
* @return 解密后内容
*/
@Deprecated
public static String rsaDecryptByPublic(String publicKeyString, String content) {
try {
@@ -363,6 +369,7 @@ public class SaSecureUtil {
* @param content 已加密内容
* @return 解密后内容
*/
@Deprecated
public static String rsaDecryptByPrivate(String privateKeyString, String content) {
try {
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);

View File

@@ -148,7 +148,7 @@ public class SaSignTemplate {
*/
public String createSign(Map<String, ?> paramsMap) {
String secretKey = getSecretKey();
SaSignException.throwByNull(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
SaSignException.notEmpty(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
// 如果调用者不小心传入了 sign 参数,则此处需要将 sign 参数排除在外
if(paramsMap.containsKey(sign)) {
@@ -295,13 +295,14 @@ public class SaSignTemplate {
String signValue = paramMap.get(sign);
// 参数非空校验
SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段");
// SaSignException.throwByNull(nonceValue, "缺少 nonce 字段"); // 配置isCheckNonce=false时可以不传 nonce
SaSignException.throwByNull(signValue, "缺少 sign 字段");
// 配置isCheckNonce=false时可以不传 nonce
if(SaFoxUtil.isEmpty(timestampValue) || SaFoxUtil.isEmpty(signValue)) {
return false;
}
// 三个值的校验必须全部通过
return isValidTimestamp(Long.parseLong(timestampValue))
&& (getSignConfigOrGlobal().getIsCheckNonce() ? isValidNonce(nonceValue) : true)
&& isValidNonce(nonceValue)
&& isValidSign(paramMap, signValue);
}
@@ -316,15 +317,13 @@ public class SaSignTemplate {
String signValue = paramMap.get(sign);
// 参数非空校验
SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段");
// SaSignException.throwByNull(nonceValue, "缺少 nonce 字段"); // 配置isCheckNonce=false时可以不传 nonce
SaSignException.throwByNull(signValue, "缺少 sign 字段");
SaSignException.notEmpty(timestampValue, "缺少 timestamp 字段");
SaSignException.notEmpty(nonceValue, "缺少 nonce 字段");
SaSignException.notEmpty(signValue, "缺少 sign 字段");
// 依次校验三个参数
checkTimestamp(Long.parseLong(timestampValue));
if(getSignConfigOrGlobal().getIsCheckNonce()) {
checkNonce(nonceValue);
}
checkNonce(nonceValue);
checkSign(paramMap, signValue);
// 通过 √
@@ -333,20 +332,52 @@ public class SaSignTemplate {
/**
* 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的
* @param request 待校验的请求对象
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
* @return 是否合法
*/
public boolean isValidRequest(SaRequest request) {
return isValidParamMap(request.getParamMap());
public boolean isValidRequest(SaRequest request, String... paramNames) {
if(paramNames.length == 0) {
return isValidParamMap(request.getParamMap());
} else {
return isValidParamMap(takeRequestParam(request, paramNames));
}
}
/**
* 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常
* @param paramNames 指定参与签名的参数有哪些,如果不填写则默认为全部参数
* @param request 待校验的请求对象
*/
public void checkRequest(SaRequest request) {
checkParamMap(request.getParamMap());
public void checkRequest(SaRequest request, String... paramNames) {
if (paramNames.length == 0) {
checkParamMap(request.getParamMap());
} else {
checkParamMap(takeRequestParam(request, paramNames));
}
}
/**
* 从请求中提取指定的参数
* @param request 请求对象
* @param paramNames 指定的参数名称,不可为空,如果传入空数组则代表只拿 timestamp、nonce、sign 三个参数
* @return 提取出的参数
*/
public Map<String, String> takeRequestParam(SaRequest request, String [] paramNames) {
Map<String, String> paramMap = new TreeMap<>();
// 此三个参数是必须获取的
paramMap.put(timestamp, request.getParam(timestamp));
paramMap.put(nonce, request.getParam(nonce));
paramMap.put(sign, request.getParam(sign));
// 获取指定的参数
for (String paramName : paramNames) {
paramMap.put(paramName, request.getParam(paramName));
}
// 返回
return paramMap;
}
// ------------------- 返回相应key -------------------

View File

@@ -471,7 +471,7 @@ public class StpLogic {
String tokenValue = distUsableToken(id, loginModel);
// 4、获取此账号的 Account-Session , 续期
SaSession session = getSessionByLoginId(id, true);
SaSession session = getSessionByLoginId(id, true, loginModel.getTimeoutOrGlobalConfig());
session.updateMinTimeout(loginModel.getTimeout());
// 5、在 Account-Session 上记录本次登录的 token 签名
@@ -1147,7 +1147,7 @@ public class StpLogic {
*/
public void updateTokenToIdMapping(String tokenValue, Object loginId) {
// 先判断一下,是否传入了空值
SaTokenException.throwBy(SaFoxUtil.isEmpty(loginId), "loginId 不能为空", SaErrorCode.CODE_11003);
SaTokenException.notTrue(SaFoxUtil.isEmpty(loginId), "loginId 不能为空", SaErrorCode.CODE_11003);
// 更新缓存中的 token 指向
getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString());
@@ -1170,10 +1170,16 @@ public class StpLogic {
*
* @param sessionId SessionId
* @param isCreate 是否新建
* @param appendOperation 如果这个 SaSession 是新建的,则要追加执行的动作
* @param timeout 如果这个 SaSession 是新建的,则使用此值作为过期值(单位:秒),可填 null代表使用全局 timeout 值
* @param appendOperation 如果这个 SaSession 是新建的,则要追加执行的动作,可填 null代表无追加动作
* @return Session对象
*/
public SaSession getSessionBySessionId(String sessionId, boolean isCreate, Consumer<SaSession> appendOperation) {
public SaSession getSessionBySessionId(String sessionId, boolean isCreate, Long timeout, Consumer<SaSession> appendOperation) {
// 如果提供的 sessionId 为 null则直接返回 null
if(SaFoxUtil.isEmpty(sessionId)) {
throw new SaTokenException("SessionId 不能为空").setCode(SaErrorCode.CODE_11072);
}
// 先检查这个 SaSession 是否已经存在,如果不存在且 isCreate=true则新建并返回
SaSession session = getSaTokenDao().getSession(sessionId);
@@ -1187,8 +1193,19 @@ public class StpLogic {
appendOperation.accept(session);
}
// 如果未提供 timeout则根据相应规则设定默认的 timeout
if(timeout == null) {
// 如果是 Token-Session则使用对用 token 的有效期,使 token 和 token-session 保持相同ttl同步失效
if(SaTokenConsts.SESSION_TYPE__TOKEN.equals(session.getType())) {
timeout = getTokenTimeout(session.getToken());
} else {
// 否则使用全局配置的 timeout
timeout = getConfigOrGlobal().getTimeout();
}
}
// 将这个 SaSession 入库
getSaTokenDao().setSession(session, getConfigOrGlobal().getTimeout());
getSaTokenDao().setSession(session, timeout);
}
return session;
}
@@ -1200,7 +1217,28 @@ public class StpLogic {
* @return Session对象
*/
public SaSession getSessionBySessionId(String sessionId) {
return getSessionBySessionId(sessionId, false, null);
return getSessionBySessionId(sessionId, false, null, null);
}
/**
* 获取指定账号 id 的 Account-Session, 如果该 SaSession 尚未创建isCreate=是否新建并返回
*
* @param loginId 账号id
* @param isCreate 是否新建
* @param timeout 如果这个 SaSession 是新建的,则使用此值作为过期值(单位:秒),可填 null代表使用全局 timeout 值
* @return SaSession 对象
*/
public SaSession getSessionByLoginId(Object loginId, boolean isCreate, Long timeout) {
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaTokenException("Account-Session 获取失败loginId 不能为空");
}
return getSessionBySessionId(splicingKeySession(loginId), isCreate, timeout, session -> {
// 这里是该 Account-Session 首次创建时才会被执行的方法:
// 设定这个 SaSession 的各种基础信息类型、账号体系、账号id
session.setType(SaTokenConsts.SESSION_TYPE__ACCOUNT);
session.setLoginType(getLoginType());
session.setLoginId(loginId);
});
}
/**
@@ -1211,13 +1249,7 @@ public class StpLogic {
* @return SaSession 对象
*/
public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return getSessionBySessionId(splicingKeySession(loginId), isCreate, session -> {
// 这里是该 Account-Session 首次创建时才会被执行的方法:
// 设定这个 SaSession 的各种基础信息类型、账号体系、账号id
session.setType(SaTokenConsts.SESSION_TYPE__ACCOUNT);
session.setLoginType(getLoginType());
session.setLoginId(loginId);
});
return getSessionByLoginId(loginId, isCreate, null);
}
/**
@@ -1227,7 +1259,7 @@ public class StpLogic {
* @return SaSession 对象
*/
public SaSession getSessionByLoginId(Object loginId) {
return getSessionByLoginId(loginId, true);
return getSessionByLoginId(loginId, true, null);
}
/**
@@ -1260,7 +1292,11 @@ public class StpLogic {
* @return session对象
*/
public SaSession getTokenSessionByToken(String tokenValue, boolean isCreate) {
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, session -> {
if(SaFoxUtil.isEmpty(tokenValue)) {
throw new SaTokenException("Token-Session 获取失败token 不能为空");
}
// todo 待优化
return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, null, session -> {
// 这里是该 Token-Session 首次创建时才会被执行的方法:
// 设定这个 SaSession 的各种基础信息类型、账号体系、Token 值
session.setType(SaTokenConsts.SESSION_TYPE__TOKEN);
@@ -1527,6 +1563,40 @@ public class StpLogic {
return activeTimeout;
}
/**
* 获取指定 token 的最后活跃时间13位时间戳如果不存在则返回 -2
*
* @param tokenValue 指定token
* @return /
*/
public long getTokenLastActiveTime(String tokenValue) {
// 1、如果提供的 token 为 null则返回 -2
if(SaFoxUtil.isEmpty(tokenValue)) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 2、获取这个 token 的最后活跃时间13位时间戳
String key = splicingKeyLastActiveTime(tokenValue);
String lastActiveTimeString = getSaTokenDao().get(key);
// 3、查不到返回-2
if(lastActiveTimeString == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 4、根据逗号切割字符串
return new SaValue2Box(lastActiveTimeString).getValue1AsLong();
}
/**
* 获取当前 token 的最后活跃时间13位时间戳如果不存在则返回 -2
*
* @return /
*/
public long getTokenLastActiveTime() {
return getTokenLastActiveTime(getTokenValue());
}
// ------------------- 过期时间相关 -------------------
@@ -1619,26 +1689,14 @@ public class StpLogic {
return SaTokenDao.NEVER_EXPIRE;
}
// 如果提供的 token 为 null则返回 -2
if(SaFoxUtil.isEmpty(tokenValue)) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// ------ 开始查询
// 1、先获取这个 token 的最后活跃时间13位时间戳
String key = splicingKeyLastActiveTime(tokenValue);
String lastActiveTimeString = getSaTokenDao().get(key);
// 先获取这个 token 的最后活跃时间13位时间戳
long lastActiveTime = getTokenLastActiveTime(tokenValue);
if(lastActiveTime == SaTokenDao.NOT_VALUE_EXPIRE) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 2、如果查不到返回-2
if(lastActiveTimeString == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 3、计算最后活跃时间 距离 此时此刻 的时间差
// 计算公式为: (当前时间 - 最后活跃时间) / 1000
SaValue2Box box = new SaValue2Box(lastActiveTimeString);
long lastActiveTime = box.getValue1AsLong();
// 实际时间差
long timeDiff = (System.currentTimeMillis() - lastActiveTime) / 1000;
// 该 token 允许的时间差
@@ -1648,7 +1706,7 @@ public class StpLogic {
return SaTokenDao.NEVER_EXPIRE;
}
// 4、校验这个时间差是否超过了允许的值
// 校验这个时间差是否超过了允许的值
// 计算公式为: 允许的最大时间差 - 实际时间差,判断是否 < 0 如果是则代表已经被冻结 ,返回-2
long activeTimeout = allowTimeDiff - timeDiff;
if(activeTimeout < 0) {
@@ -1725,11 +1783,7 @@ public class StpLogic {
* @return /
*/
public List<String> getRoleList() {
try {
return getRoleList(getLoginId());
} catch (NotLoginException e) {
return SaFoxUtil.emptyList();
}
return getRoleList(getLoginId());
}
/**
@@ -1749,7 +1803,11 @@ public class StpLogic {
* @return /
*/
public boolean hasRole(String role) {
return hasElement(getRoleList(), role);
try {
return hasRole(getLoginId(), role);
} catch (NotLoginException e) {
return false;
}
}
/**
@@ -1799,7 +1857,7 @@ public class StpLogic {
* @param role 角色标识
*/
public void checkRole(String role) {
if( ! hasRole(role)) {
if( ! hasRole(getLoginId(), role)) {
throw new NotRoleException(role, this.loginType).setCode(SaErrorCode.CODE_11041);
}
}
@@ -1863,11 +1921,7 @@ public class StpLogic {
* @return /
*/
public List<String> getPermissionList() {
try {
return getPermissionList(getLoginId());
} catch (NotLoginException e) {
return SaFoxUtil.emptyList();
}
return getPermissionList(getLoginId());
}
/**
@@ -1887,7 +1941,11 @@ public class StpLogic {
* @return 是否含有指定权限
*/
public boolean hasPermission(String permission) {
return hasElement(getPermissionList(), permission);
try {
return hasPermission(getLoginId(), permission);
} catch (NotLoginException e) {
return false;
}
}
/**
@@ -1937,7 +1995,7 @@ public class StpLogic {
* @param permission 权限码
*/
public void checkPermission(String permission) {
if( ! hasPermission(permission)) {
if( ! hasPermission(getLoginId(), permission)) {
throw new NotPermissionException(permission, this.loginType).setCode(SaErrorCode.CODE_11051);
}
}
@@ -2078,19 +2136,32 @@ public class StpLogic {
* @return 当前令牌的登录设备类型
*/
public String getLoginDevice() {
// 1、如果前端没有提交 token直接返回 null
String tokenValue = getTokenValue();
if(tokenValue == null) {
return getLoginDeviceByToken(getTokenValue());
}
/**
* 返回指定 token 会话的登录设备类型
*
* @param tokenValue 指定token
* @return 当前令牌的登录设备类型
*/
public String getLoginDeviceByToken(String tokenValue) {
// 1、如果 token 为 null直接提前返回
if(SaFoxUtil.isEmpty(tokenValue)) {
return null;
}
// 2、如果当前会话还未登录,直接返回 null
if(!isLogin()) {
// 2、获取此 token 对应的 loginId如果为null或者此token已被冻结直接返回null
Object loginId = getLoginIdNotHandle(tokenValue);
if( ! isValidLoginId(loginId)) {
return null;
}
if(getTokenActiveTimeoutByToken(tokenValue) == SaTokenDao.NOT_VALUE_EXPIRE ) {
return null;
}
// 3、获取当前账号的 Account-Session
SaSession session = getSessionByLoginId(getLoginIdDefaultNull(), false);
// 3、获取这个账号的 Account-Session
SaSession session = getSessionByLoginId(loginId, false);
// 4、为 null 说明尚未登录,当然也就不存在什么设备类型,直接返回 null
if(session == null) {
@@ -2108,7 +2179,7 @@ public class StpLogic {
// 6、没有找到还是返回 null
return null;
}
// ------------------- 会话管理 -------------------

View File

@@ -844,7 +844,27 @@ public class StpUtil {
return stpLogic.getLoginDevice();
}
/**
* 返回指定 token 会话的登录设备类型
*
* @param tokenValue 指定token
* @return 当前令牌的登录设备类型
*/
public static String getLoginDeviceByToken(String tokenValue) {
return stpLogic.getLoginDeviceByToken(tokenValue);
}
/**
* 获取当前 token 的最后活跃时间13位时间戳如果不存在则返回 -2
*
* @return /
*/
public static long getTokenLastActiveTime() {
return stpLogic.getTokenLastActiveTime();
}
// ------------------- 会话管理 -------------------
/**

View File

@@ -17,9 +17,11 @@ package cn.dev33.satoken.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.strategy.*;
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;
@@ -178,10 +180,16 @@ public final class SaStrategy {
SaManager.getStpLogic(checkDisable.type(), false).checkByAnnotation(checkDisable);
}
// 校验 @SaCheckBasic 注解
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.instance.getAnnotation.apply(element, SaCheckBasic.class);
if(checkBasic != null) {
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
// 校验 @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 注解
@@ -257,10 +265,10 @@ public final class SaStrategy {
}
// 6、校验注解@SaCheckBasic
SaCheckBasic[] checkBasicArray = at.basic();
for (SaCheckBasic item : checkBasicArray) {
SaCheckHttpBasic[] checkBasicArray = at.basic();
for (SaCheckHttpBasic item : checkBasicArray) {
try {
SaBasicUtil.check(item.realm(), item.account());
SaHttpBasicUtil.check(item.realm(), item.account());
return;
} catch (SaTokenException e) {
errorList.add(e);
@@ -329,6 +337,49 @@ public final class SaStrategy {
return new StpLogic(loginType);
};
/**
* 请求 path 不允许出现的字符
*/
public static String[] INVALID_CHARACTER = {
"//", "\\",
"%2e", "%2E", // .
"%2f", "%2F", // /
"%5c", "%5C", // \
"%25" // 空格
};
/**
* 校验请求 path 的算法
*/
public SaCheckRequestPathFunction checkRequestPath = (requestPath, extArg1, extArg2) -> {
// 不允许为null
if(requestPath == null) {
throw new RequestPathInvalidException("非法请求null", null);
}
// 不允许包含非法字符
for (String item : INVALID_CHARACTER) {
if (requestPath.contains(item)) {
throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
}
}
// 不允许出现跨目录
if(requestPath.contains("/.") || requestPath.contains("\\.")) {
throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
}
};
/**
* 当请求 path 校验不通过时处理方案的算法,自定义示例:
* <pre>
* SaStrategy.instance.requestPathInvalidHandle = (e, extArg1, extArg2) -> {
* // 自定义处理逻辑 ...
* };
* </pre>
*/
public SaRequestPathInvalidHandleFunction requestPathInvalidHandle = null;
// ----------------------- 重写策略 set连缀风格

View File

@@ -36,7 +36,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.36.0";
public static final String VERSION_NO = "v1.37.0";
/**
* Sa-Token 开源地址 Gitee
@@ -186,6 +186,28 @@ public class SaTokenConsts {
*/
public static final int ASSEMBLY_ORDER = -100;
/**
* 请求 path 校验过滤器的注册顺序
*/
public static final int PATH_CHECK_FILTER_ORDER = -1000;
/**
* Content-Type key
*/
public static final String CONTENT_TYPE_KEY = "Content-Type";
/**
* Content-Type text/plain; charset=utf-8
*/
public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain; charset=utf-8";
/**
* Content-Type application/json;charset=UTF-8
*/
public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json;charset=UTF-8";
// =================== 废弃 ===================

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -3,31 +3,37 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-cross-header-server</artifactId>
<artifactId>sa-token-demo-beetl</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.15</version>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenCrossHeaderApplication</java.run.main.class>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Beetl 视图引擎 -->
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl-framework-starter</artifactId>
<version>1.2.40.Beetl.RELEASE</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -35,8 +41,21 @@
<version>${sa-token.version}</version>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -1,21 +1,17 @@
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 跨域测试header参数版
* @author click33
*/
@SpringBootApplication
public class SaTokenCrossHeaderApplication {
public class SaTokenBeetlDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenCrossHeaderApplication.class, args);
SpringApplication.run(SaTokenBeetlDemoApplication.class, args);
System.out.println("\n启动成功Sa-Token 配置如下:" + SaManager.getConfig());
System.out.println("\n后端地址使用 http://localhost 访问,前端页面用 http://127.0.0.1 访问");
System.out.println("\n测试访问:http://localhost:8081/");
}
}
}

View File

@@ -0,0 +1,25 @@
package com.pj.satoken;
import cn.dev33.satoken.stp.StpUtil;
import com.ibeetl.starter.BeetlTemplateCustomize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 为 Beetl 视图引擎注册自定义函数:
// 通过 stp.xxx() 调用 StpUtil.stpLogic 对象上所有 public 方法
@Bean
public BeetlTemplateCustomize beetlTemplateCustomize(){
return groupTemplate -> groupTemplate.registerFunctionPackage("stp", StpUtil.stpLogic);
}
}

View File

@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解保证此类被springboot扫描即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}

View File

@@ -0,0 +1,23 @@
package com.pj.test;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public SaResult handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -0,0 +1,39 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
* 测试 Controller
* @author click33
*
*/
@RestController
public class TestController {
// 首页
@RequestMapping("/")
public Object index() {
return new ModelAndView("index.btl");
}
// 登录
@RequestMapping("login")
public SaResult login(@RequestParam(defaultValue="10001") String id) {
StpUtil.login(id);
StpUtil.getSession().set("name", "zhangsan");
return SaResult.ok();
}
// 注销
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}

View File

@@ -0,0 +1,5 @@
# 端口
server:
port: 8081

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Sa-Token 集成 Beetl 示例</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<div class="view-box" style="padding: 30px;">
<h2>Sa-Token 集成 Beetl —— 测试页面</h2>
<p>当前是否登录:${stp.isLogin()}</p>
<p>
<span>操作:</span>
<a href="login" target="_blank">登录</a>
<a href="logout" target="_blank">注销</a>
</p>
<p>
登录之后才能显示:
<% if(stp.isLogin()){ %>
value
<%}%>
</p>
<p>
不登录才能显示:
<% if(!stp.isLogin()){ %>
value
<%}%>
</p>
<p>具有角色 admin 才能显示:<% if(stp.hasRole("admin")){ %> value <% } %></p>
<p>同时具备多个角色才能显示:<% if(stp.hasRoleAnd("admin", "ceo", "cto")){ %> value <% } %></p>
<p>只要具有其中一个角色就能显示:<% if(stp.hasRoleOr("admin", "ceo", "cto")){ %> value <% } %></p>
<p>不具有角色 admin 才能显示:<% if(!stp.hasRole("admin")){ %> value <% } %></p>
<p>具有权限 user-add 才能显示:<% if(stp.hasPermission("user-add")){ %> value <% } %></p>
<p>同时具备多个权限才能显示:<% if(stp.hasPermissionAnd("user-add", "user-delete", "user-get")){ %> value <% } %></p>
<p>只要具有其中一个权限就能显示:<% if(stp.hasPermissionOr("user-add", "user-delete", "user-get")){ %> value <% } %></p>
<p>不具有权限 user-add 才能显示:<% if(!stp.hasPermission("user-add")){ %> value <% } %></p>
<% if(stp.isLogin()){ %>
<p>
从SaSession中取值${stp.getSession()["name"]} <br>
或:${stp.getSession().name}
</p>
<%}%>
</div>
</body>
</html>

View File

@@ -16,7 +16,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>
@@ -73,7 +73,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>1.36.0</version>
<version>1.37.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@@ -17,7 +17,7 @@
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -1,11 +1,10 @@
package com.pj.cases.up;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token Http Basic 认证
*
@@ -28,7 +27,7 @@ public class HttpBasicController {
@RequestMapping("getInfo")
public SaResult login() {
// 1、Http Basic 认证校验,账号=sa密码=123456
SaBasicUtil.check("sa:123456");
SaHttpBasicUtil.check("sa:123456");
// 2、返回数据
String data = "这是通过 Http Basic 校验后才返回的数据";

View File

@@ -1,62 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> Sa-Token 跨域测试 - Cookie 版h5 页面 </title>
</head>
<body>
<div style="text-align: center; padding-top: 200px;">
<h2> Sa-Token 跨域测试 - Cookie 版h5 页面 </h2>
<p>当前是否登录:<b class="is-login"></b></p>
<p>
<a href="javascript: doLogin();">登录</a>&nbsp;&nbsp;
<a href="javascript: doLogout();">注销</a>
</p>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="./method-util.js"></script>
<script type="text/javascript">
// 查询当前会话是否登录
function isLogin() {
ajax('/acc/isLogin', {}, function (res) {
$('.is-login').html(res.data + '');
})
}
isLogin();
// 去登录
function doLogin() {
const param = {
name: "zhang",
pwd: "123456"
}
ajax('/acc/doLogin', param, function (res) {
if(res.code === 200) {
// 浏览器会自动在 cookie 中保存 token
localStorage.satoken = res.token;
$('.is-login').html('true');
alert('登录成功');
} else {
alert(res.msg);
}
})
}
// 去注销
function doLogout() {
ajax('/acc/logout', {}, function (res) {
if(res.code === 200) {
// 浏览器会自动清除 cookie 中的 token
$('.is-login').html('false');
alert('注销成功');
} else {
alert(res.msg);
}
})
}
</script>
</body>
</html>

View File

@@ -1,28 +0,0 @@
// 后端服务地址 (在 Cookie 版跨域模式中,此处应该是一个 https 地址)
// var baseUrl = "http://localhost:8081";
var baseUrl = "https://20e331r221.yicp.fun";
// 封装一下 Ajax 方法
var ajax = function(path, data, successFn) {
$.ajax({
url: baseUrl + path,
type: "post",
data: data,
dataType: 'json',
// 指定是跨域模式,需要提交第三方 Cookie
crossDomain: true,
xhrFields:{
withCredentials: true
},
headers: {
"X-Requested-With": "XMLHttpRequest"
},
success: function(res){
successFn(res);
},
error: function(xhr, type, errorThrown){
return alert("异常:" + JSON.stringify(xhr));
}
});
}

View File

@@ -1,12 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@@ -1,21 +0,0 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Sa-Token 跨域测试Cookie 版)
* @author click33
*/
@SpringBootApplication
public class SaTokenCrossCookieApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenCrossCookieApplication.class, args);
System.out.println("\n启动成功Sa-Token 配置如下:" + SaManager.getConfig());
System.out.println("\n后端地址使用 https://xxx.com 访问(必须为 https 连接),前端页面用 http://127.0.0.1 访问");
}
}

View File

@@ -1,79 +0,0 @@
package com.pj.test;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
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 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**").addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
// ...
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(obj -> {
// 获得客户端domain
SaRequest request = SaHolder.getRequest();
String origin = request.getHeader("Origin");
if (origin == null) {
origin = request.getHeader("Referer");
}
// ---------- 设置跨域响应头 ----------
SaHolder.getResponse()
// 允许第三方 Cookie
.setHeader("Access-Control-Allow-Credentials", "true")
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", origin)
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
;
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求不做处理"))
.back();
})
;
}
}

View File

@@ -1,11 +0,0 @@
# 端口
server:
port: 8081
sa-token:
is-log: true
cookie:
# 指明当前为 https 安全连接
secure: true
# 指明第三方 Cookie 限制级别为:不限制
sameSite: None

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,20 +0,0 @@
# Sa-Token 跨域测试 - Cookie 版vue3 页面
在线文档:[https://sa-token.cc/](https://sa-token.cc/)
## 运行
先安装依赖
``` bat
npm install --registry=https://registry.npm.taobao.org
```
运行
``` bat
npm run dev
```
打包
``` bat
npm run build
```

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> Sa-Token 跨域测试 - Cookie 版vue3 页面 </title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -1,20 +0,0 @@
{
"name": "sa-token-demo-cross-cookie-vue3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.1.3",
"vue": "^3.2.41",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"vite": "^3.2.7"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,11 +0,0 @@
<template>
<router-view />
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@@ -1,14 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
// createApp
const app = createApp(App);
// 安装 vue-router
import router from './router';
app.use(router);
// 绑定dom
app.mount('#app');

View File

@@ -1,25 +0,0 @@
import { createRouter, createWebHashHistory } from 'vue-router';
/**
* 创建 vue-router 实例
*/
const router = createRouter({
history: createWebHashHistory(),
routes: [
// 首页
{
name: 'index',
path: "/index",
component: () => import('../views/index.vue'),
},
// 访问 / 时自动重定向到 /index
{
path: "/",
redirect: '/index'
}
],
});
// 导出
export default router;

View File

@@ -1,63 +0,0 @@
<!-- 项目首页 -->
<template>
<div style="text-align: center; padding-top: 200px;">
<h2> Sa-Token 跨域测试 - Cookie Vue3页面 </h2>
<p>当前是否登录<b>{{ state.isLogin }}</b></p>
<p>
<a href="javascript:;" @click="doLogin">登录</a>&nbsp;&nbsp;
<a href="javascript:;" @click="doLogout">注销</a>
</p>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import {baseUrl, ajax} from './method-util.js'
// 是否登录
const state = reactive({
isLogin: false
})
onMounted(function(){
isLogin();
})
// 查询当前会话是否登录
const isLogin = function() {
ajax('/acc/isLogin', {}, function (res) {
state.isLogin = res.data;
})
}
// 去登录
const doLogin = function() {
const param = {
name: "zhang",
pwd: "123456"
}
ajax('/acc/doLogin', param, function (res) {
if(res.code === 200) {
// 浏览器会自动在 cookie 中保存 token
state.isLogin = true;
alert('登录成功');
} else {
alert(res.msg);
}
})
}
// 去注销
const doLogout = function() {
ajax('/acc/logout', {}, function (res) {
if(res.code === 200) {
// 浏览器会自动清除 cookie 中的 token
state.isLogin = false;
alert('注销成功');
} else {
alert(res.msg);
}
})
}
</script>

View File

@@ -1,28 +0,0 @@
import axios from 'axios'
// 后端服务地址 (在 Cookie 版跨域模式中,此处应该是一个 https 地址)
// export const baseUrl = "http://localhost:8081";
export const baseUrl = "https://20e331r221.yicp.fun";
// 封装一下 Ajax 方法
export const ajax = function(path, data, successFn) {
axios({
url: baseUrl + path,
method: 'post',
data: data,
// 重点:开启第三方 Cookie
withCredentials: true,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).
then(function (response) { // 成功时执行
const res = response.data;
successFn(res);
}).
catch(function (error) {
return alert("异常:" + JSON.stringify(error));
})
}

View File

@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})

View File

@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> Sa-Token 跨域测试 - Header 参数版h5 页面 </title>
</head>
<body>
<div style="text-align: center; padding-top: 200px;">
<h2> Sa-Token 跨域测试 - Header 参数版h5 页面 </h2>
<p>当前是否登录:<b class="is-login"></b></p>
<p>
<a href="javascript: doLogin();">登录</a>&nbsp;&nbsp;
<a href="javascript: doLogout();">注销</a>
</p>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="./method-util.js"></script>
<script type="text/javascript">
// 查询当前会话是否登录
function isLogin() {
ajax('/acc/isLogin', {}, function (res) {
$('.is-login').html(res.data + '');
})
}
isLogin();
// 去登录
function doLogin() {
const param = {
name: "zhang",
pwd: "123456"
}
ajax('/acc/doLogin', param, function (res) {
if(res.code === 200) {
// 保存 token
localStorage.satoken = res.token;
$('.is-login').html('true');
alert('登录成功token是' + res.token);
} else {
alert(res.msg);
}
})
}
// 去注销
function doLogout() {
ajax('/acc/logout', {}, function (res) {
if(res.code === 200) {
// 清除 token
localStorage.removeItem('satoken');
$('.is-login').html('false');
alert('注销成功');
} else {
alert(res.msg);
}
})
}
</script>
</body>
</html>

View File

@@ -1,23 +0,0 @@
// 后端服务地址
var baseUrl = "http://localhost:8081";
// 封装一下 Ajax 方法
var ajax = function(path, data, successFn) {
$.ajax({
url: baseUrl + path,
type: "post",
data: data,
dataType: 'json',
headers: {
"X-Requested-With": "XMLHttpRequest",
"satoken": localStorage.getItem("satoken")
},
success: function(res){
successFn(res);
},
error: function(xhr, type, errorThrown){
return alert("异常:" + JSON.stringify(xhr));
}
});
}

View File

@@ -1,12 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@@ -1,41 +0,0 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 登录测试
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
// 要点:通过请求响应体返回 token 信息
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
}
return SaResult.error("登录失败");
}
// 注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
boolean isLogin = StpUtil.isLogin();
System.out.println("当前会话是否登录:" + isLogin);
return SaResult.data(isLogin);
}
}

View File

@@ -1,69 +0,0 @@
package com.pj.test;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
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 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**").addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
// ...
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(obj -> {
SaHolder.getResponse()
// ---------- 设置跨域响应头 ----------
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "*")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
;
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求不做处理"))
.back();
})
;
}
}

View File

@@ -1,6 +0,0 @@
# 端口
server:
port: 8081
sa-token:
is-log: true

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,20 +0,0 @@
# Sa-Token 跨域测试 - Header 参数版vue3 页面
在线文档:[https://sa-token.cc/](https://sa-token.cc/)
## 运行
先安装依赖
``` bat
npm install --registry=https://registry.npm.taobao.org
```
运行
``` bat
npm run dev
```
打包
``` bat
npm run build
```

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> Sa-Token 跨域测试 - Header 参数版vue3 页面 </title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -1,20 +0,0 @@
{
"name": "sa-token-demo-cross-header-vue3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.1.3",
"vue": "^3.2.41",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"vite": "^3.2.7"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,11 +0,0 @@
<template>
<router-view />
</template>
<script setup>
</script>
<style scoped>
</style>

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@@ -1,14 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
// createApp
const app = createApp(App);
// 安装 vue-router
import router from './router';
app.use(router);
// 绑定dom
app.mount('#app');

View File

@@ -1,25 +0,0 @@
import { createRouter, createWebHashHistory } from 'vue-router';
/**
* 创建 vue-router 实例
*/
const router = createRouter({
history: createWebHashHistory(),
routes: [
// 首页
{
name: 'index',
path: "/index",
component: () => import('../views/index.vue'),
},
// 访问 / 时自动重定向到 /index
{
path: "/",
redirect: '/index'
}
],
});
// 导出
export default router;

View File

@@ -1,65 +0,0 @@
<!-- 项目首页 -->
<template>
<div style="text-align: center; padding-top: 200px;">
<h2> Sa-Token 跨域测试-header参数版vue3 页面 </h2>
<p>当前是否登录<b>{{ state.isLogin }}</b></p>
<p>
<a href="javascript:;" @click="doLogin">登录</a>&nbsp;&nbsp;
<a href="javascript:;" @click="doLogout">注销</a>
</p>
</div>
</template>
<script setup>
import { onMounted, reactive } from 'vue'
import { ajax } from './method-util.js'
// 是否登录
const state = reactive({
isLogin: false
})
onMounted(function(){
isLogin();
})
// 查询当前会话是否登录
const isLogin = function() {
ajax('/acc/isLogin', {}, function (res) {
state.isLogin = res.data;
})
}
// 去登录
const doLogin = function() {
const param = {
name: "zhang",
pwd: "123456"
}
ajax('/acc/doLogin', param, function (res) {
if(res.code === 200) {
// 保存 token
localStorage.satoken = res.token;
state.isLogin = true;
alert('登录成功token是' + res.token);
} else {
alert(res.msg);
}
})
}
// 去注销
const doLogout = function() {
ajax('/acc/logout', {}, function (res) {
if(res.code === 200) {
// 清除 token
localStorage.removeItem('satoken');
state.isLogin = false;
alert('注销成功');
} else {
alert(res.msg);
}
})
}
</script>

View File

@@ -1,25 +0,0 @@
import axios from 'axios'
// 后端服务地址
export const baseUrl = "http://localhost:8081";
// 封装一下 Ajax 方法
export const ajax = function(path, data, successFn) {
axios({
url: baseUrl + path,
method: 'post',
data: data,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"satoken": localStorage.getItem("satoken")
}
}).
then(function (response) { // 成功时执行
const res = response.data;
successFn(res);
}).
catch(function (error) {
return alert("异常:" + JSON.stringify(error));
})
}

View File

@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})

View File

@@ -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.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>

View File

@@ -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.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
<dubbo.version>2.7.21</dubbo.version>
<nacos.version>1.4.2</nacos.version>
</properties>

View File

@@ -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.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>

View File

@@ -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.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
<dubbo.version>3.2.2</dubbo.version>
<nacos.version>2.2.2</nacos.version>
</properties>

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.36.0</sa-token.version>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>

View File

@@ -3,21 +3,21 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-cross-cookie-server</artifactId>
<artifactId>sa-token-demo-hutool-timed-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.15</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.36.0</sa-token.version>
<java.run.main.class>com.pj.SaTokenCrossCookieApplication</java.run.main.class>
<sa-token.version>1.37.0</sa-token.version>
</properties>
<dependencies>
@@ -27,6 +27,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
@@ -35,8 +39,21 @@
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Hutool-TimedCache -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-hutool-timed-cache</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,21 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Sa-Token 整合 Hutool-TimedCache 示例
* @author click33
*
*/
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
System.out.println(SaManager.getSaTokenDao());
}
}

View File

@@ -0,0 +1,57 @@
package com.pj.current;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
}
else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
}
else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
}
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + ")" + ee.getDisableTime() + "秒后解封");
}
else { // 普通异常, 输出500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}

View File

@@ -0,0 +1,27 @@
package com.pj.current;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 处理 404
* @author click33
*/
@RestController
public class NotFoundHandle implements ErrorController {
@RequestMapping("/error")
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(200);
return SaResult.get(404, "not found", null);
}
}

View File

@@ -0,0 +1,69 @@
package com.pj.satoken;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
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("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行 BeforeAuth不受 includeList 与 excludeList 的限制,所有请求都会进入)
.setBeforeAuth(r -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图 DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}
}

View File

@@ -0,0 +1,44 @@
package com.pj.satoken;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*/
@Component // 打开此注解保证此类被springboot扫描即可完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询权限
List<String> list = new ArrayList<String>();
list.add("101");
list.add("user-add");
list.add("user-delete");
list.add("user-update");
list.add("user-get");
list.add("article-get");
return list;
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 本list仅做模拟实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}

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