适配 springboot3

This commit is contained in:
click33 2023-01-05 19:20:21 +08:00
parent d09366602a
commit 1feca73e2a
134 changed files with 3238 additions and 974 deletions

View File

@ -1,175 +0,0 @@
package cn.dev33.satoken.id;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.IdTokenInvalidException;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* <h1> 本类设计已过时未来版本可能移除此类请及时更换为 SaSameTemplate 使用方式保持不变 </h1>
*
* Sa-Token-Id 身份凭证模块
* <p> 身份凭证的获取与校验可用于微服务内部调用鉴权
* @author kong
*
*/
@Deprecated
public class SaIdTemplate {
/**
* Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = "SA_ID_TOKEN";
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在则立即创建并返回
* @return 当前token
*/
public String getToken() {
String currentToken = getTokenNh();
if(SaFoxUtil.isEmpty(currentToken)) {
// 注意这里的自刷新不能做到高并发可用
currentToken = refreshToken();
}
return currentToken;
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public boolean isValid(String token) {
// 1 如果传入的token未空立即返回false
if(SaFoxUtil.isEmpty(token)) {
return false;
}
// 2 验证当前 Id-Token Past-Id-Token
return token.equals(getToken()) || token.equals(getPastTokenNh());
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public void checkToken(String token) {
if(isValid(token) == false) {
token = (token == null ? "" : token);
throw new IdTokenInvalidException("无效Id-Token" + token);
}
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public void checkCurrentRequestToken() {
checkToken(SaHolder.getRequest().getHeader(ID_TOKEN));
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public String refreshToken() {
// 1. 先将当前 Id-Token 写入到 Past-Id-Token
String idToken = getTokenNh();
if(SaFoxUtil.isEmpty(idToken) == false) {
savePastToken(idToken, getTokenTimeout());
}
// 2. 再刷新当前Id-Token
String newIdToken = createToken();
saveToken(newIdToken);
// 3. 返回新的 Id-Token
return newIdToken;
}
// ------------------------------ 保存Token
/**
* 保存Id-Token
* @param token token
*/
public void saveToken(String token) {
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingTokenSaveKey(), token, SaManager.getConfig().getIdTokenTimeout());
}
/**
* 保存Past-Id-Token
* @param token token
* @param timeout 有效期单位
*/
public void savePastToken(String token, long timeout){
if(SaFoxUtil.isEmpty(token)) {
return;
}
SaManager.getSaTokenDao().set(splicingPastTokenSaveKey(), token, timeout);
}
// -------------------- 获取Token
/**
* 获取Id-Token不做任何处理
* @return token
*/
public String getTokenNh() {
return SaManager.getSaTokenDao().get(splicingTokenSaveKey());
}
/**
* 获取Past-Id-Token不做任何处理
* @return token
*/
public String getPastTokenNh() {
return SaManager.getSaTokenDao().get(splicingPastTokenSaveKey());
}
/**
* 获取Id-Token的剩余有效期 (单位)
* @return token
*/
public long getTokenTimeout() {
return SaManager.getSaTokenDao().getTimeout(splicingTokenSaveKey());
}
// -------------------- 创建Token
/**
* 创建一个Id-Token
* @return Token
*/
public String createToken() {
return SaFoxUtil.getRandomString(64);
}
// -------------------- 拼接key
/**
* 拼接keyId-Token 的存储 key
* @return key
*/
public String splicingTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:id-token";
}
/**
* 拼接key次级 Id-Token 的存储 key
* @return key
*/
public String splicingPastTokenSaveKey() {
return SaManager.getConfig().getTokenName() + ":var:past-id-token";
}
}

View File

@ -1,86 +0,0 @@
package cn.dev33.satoken.id;
/**
* <h1> 本类设计已过时未来版本可能移除此类请及时更换为 SaSameUtil 使用方式保持不变 </h1>
*
* Sa-Token-Id 身份凭证模块-工具类
* @author kong
*
*/
@Deprecated
public class SaIdUtil {
private SaIdUtil(){}
/**
* Request 上储存 Id-Token 时建议使用的key
*/
public static final String ID_TOKEN = SaIdTemplate.ID_TOKEN;
/**
* 底层 SaIdTemplate 对象
*/
public static SaIdTemplate saIdTemplate = new SaIdTemplate();
// -------------------- 获取 & 校验
/**
* 获取当前Id-Token, 如果不存在则立即创建并返回
* @return 当前token
*/
public static String getToken() {
return saIdTemplate.getToken();
}
/**
* 判断一个Id-Token是否有效
* @param token 要验证的token
* @return 这个token是否有效
*/
public static boolean isValid(String token) {
return saIdTemplate.isValid(token);
}
/**
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
* @param token 要验证的token
*/
public static void checkToken(String token) {
saIdTemplate.checkToken(token);
}
/**
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
*/
public static void checkCurrentRequestToken() {
saIdTemplate.checkCurrentRequestToken();
}
/**
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
* @return 新Token
*/
public static String refreshToken() {
return saIdTemplate.refreshToken();
}
// -------------------- 获取Token
/**
* 获取Id-Token不做任何处理
* @return token
*/
public static String getTokenNh() {
return saIdTemplate.getTokenNh();
}
/**
* 获取Past-Id-Token不做任何处理
* @return token
*/
public static String getPastTokenNh() {
return saIdTemplate.getPastTokenNh();
}
}

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

@ -9,7 +9,8 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<!--<version>2.3.3.RELEASE</version>-->
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->

View File

@ -9,7 +9,8 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<!--<version>2.3.3.RELEASE</version>-->
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->

View File

@ -9,7 +9,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<version>2.5.14</version>
</parent>
<!-- 指定一些属性 -->

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>2.6.0</version> -->
</parent>

View File

@ -11,6 +11,8 @@
<properties>
<sa-token.version>1.33.0</sa-token.version>
<solon.version>1.10.13</solon.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

@ -17,13 +17,13 @@ sa-token:
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
is-log: true
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

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

View File

@ -0,0 +1,71 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-springboot3-redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- @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 org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token 整合 SpringBoot3 示例整合redis
* @author kong
*
*/
@SpringBootApplication
public class SaTokenSpringBoot3Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenSpringBoot3Application.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@ -0,0 +1,56 @@
package com.pj.current;
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;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 全局异常处理
*/
@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,26 @@
package com.pj.current;
import java.io.IOException;
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;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 处理 404
* @author kong
*/
@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,71 @@
package com.pj.satoken;
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;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.util.SaResult;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@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 -> {
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
})
// 异常处理函数每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
e.printStackTrace();
return SaResult.error(e.getMessage());
})
// 前置函数在每次认证函数之前执行
.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;
}
}

View File

@ -0,0 +1,80 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaCheckBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 注解鉴权测试
* @author kong
*
*/
@RestController
@RequestMapping("/at/")
public class AtController {
// 登录认证登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
@SaCheckLogin
@RequestMapping("checkLogin")
public SaResult checkLogin() {
return SaResult.ok();
}
// 权限认证具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
@SaCheckPermission("user-add")
@RequestMapping("checkPermission")
public SaResult checkPermission() {
return SaResult.ok();
}
// 权限认证同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
@SaCheckPermission({"user-add", "user-delete", "user-update"})
@RequestMapping("checkPermissionAnd")
public SaResult checkPermissionAnd() {
return SaResult.ok();
}
// 权限认证只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
@RequestMapping("checkPermissionOr")
public SaResult checkPermissionOr() {
return SaResult.ok();
}
// 角色认证只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
@SaCheckRole("admin")
@RequestMapping("checkRole")
public SaResult checkRole() {
return SaResult.ok();
}
// 完成二级认证 ---- http://localhost:8081/at/openSafe
@RequestMapping("openSafe")
public SaResult openSafe() {
StpUtil.openSafe(200); // 打开二级认证有效期为200秒
return SaResult.ok();
}
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
@SaCheckSafe
@RequestMapping("checkSafe")
public SaResult checkSafe() {
return SaResult.ok();
}
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
@SaCheckBasic(account = "sa:123456")
@RequestMapping("checkBasic")
public SaResult checkBasic() {
return SaResult.ok();
}
}

View File

@ -0,0 +1,48 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 登录测试
* @author kong
*
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 此处仅作模拟示例真实项目需要从数据库中查询数据进行比对
if("zhang".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}

View File

@ -0,0 +1,61 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.Ttime;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 压力测试
* @author kong
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问 http://localhost:8081/s-test/login
// 测试前请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public SaResult login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
int count = 10; // 循环多少轮
int loginCount = 10000; // 每轮循环多少次
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 1; i <= count; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= loginCount; j++) {
StpUtil.login("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return SaResult.ok();
}
}

View File

@ -0,0 +1,30 @@
package com.pj.test;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试 浏览器访问 http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
return SaResult.ok();
}
// 测试 浏览器访问 http://localhost:8081/test/test2
@RequestMapping("test2")
public SaResult test2() {
return SaResult.ok();
}
}

View File

@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回根据受影响行数的(大于0=ok小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}

View File

@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author kong
*
*/
public class Ttime {
private long start=0; //开始时间
private long end=0; //结束时间
public static Ttime t = new Ttime(); //static快捷使用
/**
* 开始计时
* @return
*/
public Ttime start() {
start=System.currentTimeMillis();
return this;
}
/**
* 结束计时
*/
public Ttime end() {
end=System.currentTimeMillis();
return this;
}
/**
* 返回所用毫秒数
*/
public long returnMs() {
return end-start;
}
/**
* 格式化输出结果
*/
public void outTime() {
System.out.println(this.toString());
}
/**
* 结束并格式化输出结果
*/
public void endOutTime() {
this.end().outTime();
}
@Override
public String toString() {
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为0.01s
}
}

View File

@ -0,0 +1,50 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 是否输出操作日志
is-log: true
spring:
data:
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

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

View File

@ -0,0 +1,72 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-webflux-springboot3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证Reactor响应式集成, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token整合webflux 示例 (springboot3)
*
* @author kong
* @since 2023年1月3日
*
*/
@SpringBootApplication
public class SaTokenWebfluxSpringboot3Application {
public static void main(String[] args) {
SpringApplication.run(SaTokenWebfluxSpringboot3Application.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@ -0,0 +1,41 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfigure {
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 指定 [拦截路由]
.addInclude("/**")
// 指定 [放行路由]
.addExclude("/favicon.ico")
// 指定[认证函数]: 每次请求执行
.setAuth(r -> {
System.out.println("---------- sa全局认证");
// SaRouter.match("/test/test", () -> StpUtil.checkLogin());
})
// 指定[异常处理函数]每次[认证函数]发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(e.getMessage());
})
;
}
}

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,35 @@
package com.pj.test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.StpUtil;
@Configuration
public class DefineRoutes {
/**
* 函数式编程初始化路由表
* @return 路由表
*/
@SuppressWarnings("deprecation")
@Bean
public RouterFunction<ServerResponse> getRoutes() {
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
// 测试打印
System.out.println("是否登录:" + StpUtil.isLogin());
// 返回结果
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj);
});
}
}

View File

@ -0,0 +1,52 @@
package com.pj.test;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@ControllerAdvice // 可指定包前缀比如(basePackages = "com.pj.admin")
public class GlobalException {
// 全局异常拦截拦截项目中的所有异常
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e)
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.getDisableTime() + "秒后解封");
} else { // 普通异常, 输出500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}

View File

@ -0,0 +1,80 @@
package com.pj.test;
import java.time.Duration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.stp.StpUtil;
import reactor.core.publisher.Mono;
/**
* 测试专用Controller
* @author kong
*
*/
@RestController
@RequestMapping("/test/")
public class TestController {
// 测试登录接口 [同步模式] 浏览器访问 http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
StpUtil.login(id);
return AjaxJson.getSuccess("登录成功");
}
// API测试 [同步模式] 浏览器访问 http://localhost:8081/test/isLogin
@RequestMapping("isLogin")
public AjaxJson isLogin() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
}
// API测试 [异步模式] 浏览器访问 http://localhost:8081/test/isLogin2
@RequestMapping("isLogin2")
public Mono<AjaxJson> isLogin2() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return Mono.just(aj);
}
// API测试 [异步模式, 同一线程] 浏览器访问 http://localhost:8081/test/isLogin3
@RequestMapping("isLogin3")
public Mono<AjaxJson> isLogin3() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
// 异步方式
return SaReactorHolder.getContext().map(e -> {
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
});
}
// API测试 [异步模式, 不同线程] 浏览器访问 http://localhost:8081/test/isLogin4
@RequestMapping("isLogin4")
public Mono<AjaxJson> isLogin4() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
System.out.println("线程id-----" + Thread.currentThread().getId());
return Mono.delay(Duration.ofSeconds(1)).flatMap(r->{
return SaReactorHolder.getContext().map(rr->{
System.out.println("线程id---内--" + Thread.currentThread().getId());
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
});
});
}
// 测试 浏览器访问 http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
System.out.println("线程id-----------Controller--" + Thread.currentThread().getId() + "\t\t");
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
}
}

View File

@ -0,0 +1,154 @@
package com.pj.util;
import java.io.Serializable;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回根据受影响行数的(大于0=ok小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// 转JSON格式输出
try {
return new ObjectMapper().writeValueAsString(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,46 @@
# 端口
server:
port: 8081
# sa-token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# token风格
token-style: uuid
# 日志
is-log: true
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<relativePath/>
</parent>

View File

@ -11,10 +11,10 @@ import cn.dev33.satoken.SaManager;
*
*/
@SpringBootApplication
public class SaTokenWebfluxDemoApplication {
public class SaTokenWebfluxApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenWebfluxDemoApplication.class, args);
SpringApplication.run(SaTokenWebfluxApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}

View File

@ -16,6 +16,8 @@ sa-token:
is-share: true
# token风格
token-style: uuid
# 日志
is-log: true
spring:
# redis配置

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.12</version>
<version>2.5.14</version>
<!-- <version>1.5.9.RELEASE</version> -->
<relativePath/>
</parent>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
@ -15,11 +15,13 @@
<revision>1.33.0</revision>
<!-- 统一定义依赖版本号 -->
<springboot.version>2.5.12</springboot.version>
<springboot.version>2.5.14</springboot.version>
<springboot3.version>3.0.1</springboot3.version>
<reactor-core.version>3.1.4.RELEASE</reactor-core.version>
<jackson-databind.version>2.13.4.1</jackson-databind.version>
<jackson-datatype-jsr310.version>2.11.2</jackson-datatype-jsr310.version>
<servlet-api.version>3.1.0</servlet-api.version>
<jakarta-servlet-api.version>6.0.0</jakarta-servlet-api.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<solon.version>1.12.0</solon.version>
<noear-redisx.version>1.4.4</noear-redisx.version>
@ -45,6 +47,13 @@
<version>${servlet-api.version}</version>
</dependency>
<!-- Jakarta Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta-servlet-api.version}</version>
</dependency>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -246,16 +255,31 @@
<artifactId>sa-token-servlet</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jakarta-servlet</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-solon-plugin</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${revision}</version>
</dependency>
<!-- endregion-->
<!-- region sa-token-plugin -->

View File

@ -0,0 +1 @@
cn.dev33.satoken.context.dubbo.SaTokenSecondContextCreatorForDubbo

View File

@ -0,0 +1,4 @@
cn.dev33.satoken.context.grpc.interceptor.SaTokenGrpcClientInterceptor
cn.dev33.satoken.context.grpc.interceptor.SaTokenContextGrpcServerInterceptor
cn.dev33.satoken.context.grpc.interceptor.SaTokenGrpcServerInterceptor
cn.dev33.satoken.context.grpc.SaTokenSecondContextCreatorForGrpc

View File

@ -21,7 +21,9 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -22,7 +22,7 @@ public class SaQuickInject {
*
* @param saQuickConfig 配置对象
*/
@Autowired
@Autowired(required = false)
public void setSaQuickConfig(SaQuickConfig saQuickConfig) {
SaQuickManager.setConfig(saQuickConfig);
}

View File

@ -28,18 +28,18 @@ public class SaQuickRegister {
*/
@Bean
@ConfigurationProperties(prefix = "sa")
public SaQuickConfig getSaQuickConfig() {
SaQuickConfig getSaQuickConfig() {
return new SaQuickConfig();
}
/**
* 注册 [sa-token全局过滤器]
* 注册 Sa-Token 全局过滤器
*
* @return see note
* @return /
*/
@Bean
@Order(SaTokenConsts.ASSEMBLY_ORDER - 1)
public SaServletFilter getSaServletFilter() {
SaServletFilter getSaServletFilterForQuickLogin() {
return new SaServletFilter()
// 拦截路由
.addInclude("/**")

View File

@ -1,8 +1,7 @@
package cn.dev33.satoken.quick.web;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -27,8 +26,8 @@ public class SaQuickController {
* @return see note
*/
@GetMapping("/saLogin")
public String saLogin(HttpServletRequest request) {
request.setAttribute("cfg", SaQuickManager.getConfig());
public String saLogin(Model model) {
model.addAttribute("cfg", SaQuickManager.getConfig());
return "sa-login.html";
}

View File

@ -1 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.quick.SaQuickInject
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.quick.SaQuickInject

View File

@ -70,7 +70,6 @@ public class SaCheckAspect {
SaStrategy.me.checkMethodAnnotation.accept(method);
}
try {
// 执行原有逻辑
Object obj = joinPoint.proceed();

View File

@ -19,8 +19,12 @@
<!-- 所有子模块 -->
<modules>
<module>sa-token-servlet</module>
<module>sa-token-jakarta-servlet</module>
<module>sa-token-spring-boot-autoconfig</module>
<module>sa-token-spring-boot-starter</module>
<module>sa-token-spring-boot3-starter</module>
<module>sa-token-reactor-spring-boot-starter</module>
<module>sa-token-reactor-spring-boot3-starter</module>
<module>sa-token-solon-plugin</module>
<module>sa-token-jboot-plugin</module>
<module>sa-token-jfinal-plugin</module>

View File

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

View File

@ -0,0 +1,35 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-starter</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<name>sa-token-jakarta-servlet</name>
<artifactId>sa-token-jakarta-servlet</artifactId>
<description>sa-token authentication by Jakarta Servlet API</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,17 @@
package cn.dev33.satoken.servlet.error;
/**
* 定义 sa-token-servlet 所有异常细分状态码
*
* @author kong
* @since: 2022-10-30
*/
public interface SaServletErrorCode {
/** 转发失败 */
public static final int CODE_20001 = 20001;
/** 重定向失败 */
public static final int CODE_20002 = 20002;
}

View File

@ -0,0 +1,117 @@
package cn.dev33.satoken.servlet.model;
import java.io.IOException;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.servlet.error.SaServletErrorCode;
import cn.dev33.satoken.util.SaFoxUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Request for Jakarta Servlet
* @author kong
*
*/
public class SaRequestForServlet implements SaRequest {
/**
* 底层Request对象
*/
protected HttpServletRequest request;
/**
* 实例化
* @param request request对象
*/
public SaRequestForServlet(HttpServletRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return request;
}
/**
* [请求体] 里获取一个值
*/
@Override
public String getParam(String name) {
return request.getParameter(name);
}
/**
* [请求头] 里获取一个值
*/
@Override
public String getHeader(String name) {
return request.getHeader(name);
}
/**
* [Cookie作用域] 里获取一个值
*/
@Override
public String getCookieValue(String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && name.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
/**
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestPath() {
return request.getServletPath();
}
/**
* 返回当前请求的urlhttp://xxx.com/test
* @return see note
*/
public String getUrl() {
String currDomain = SaManager.getConfig().getCurrDomain();
if(SaFoxUtil.isEmpty(currDomain) == false) {
return currDomain + this.getRequestPath();
}
return request.getRequestURL().toString();
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return request.getMethod();
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
try {
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContextOrSecond().getResponse().getSource();
request.getRequestDispatcher(path).forward(request, response);
return null;
} catch (ServletException | IOException e) {
throw new SaTokenException(e).setCode(SaServletErrorCode.CODE_20001);
}
}
}

View File

@ -0,0 +1,79 @@
package cn.dev33.satoken.servlet.model;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.servlet.error.SaServletErrorCode;
import jakarta.servlet.http.HttpServletResponse;
/**
* Response for Jakarta Servlet
* @author kong
*
*/
public class SaResponseForServlet implements SaResponse {
/**
* 底层Request对象
*/
protected HttpServletResponse response;
/**
* 实例化
* @param response response对象
*/
public SaResponseForServlet(HttpServletResponse response) {
this.response = response;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return response;
}
/**
* 设置响应状态码
*/
@Override
public SaResponse setStatus(int sc) {
response.setStatus(sc);
return this;
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
response.setHeader(name, value);
return this;
}
/**
* 在响应头里添加一个值
* @param name 名字
* @param value
* @return 对象自身
*/
public SaResponse addHeader(String name, String value) {
response.addHeader(name, value);
return this;
}
/**
* 重定向
*/
@Override
public Object redirect(String url) {
try {
response.sendRedirect(url);
} catch (Exception e) {
throw new SaTokenException(e).setCode(SaServletErrorCode.CODE_20002);
}
return null;
}
}

View File

@ -0,0 +1,60 @@
package cn.dev33.satoken.servlet.model;
import cn.dev33.satoken.context.model.SaStorage;
import jakarta.servlet.http.HttpServletRequest;
/**
* Storage for Jakarta Servlet
* @author kong
*
*/
public class SaStorageForServlet implements SaStorage {
/**
* 底层Request对象
*/
protected HttpServletRequest request;
/**
* 实例化
* @param request request对象
*/
public SaStorageForServlet(HttpServletRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return request;
}
/**
* [Request作用域] 里写入一个值
*/
@Override
public SaStorageForServlet set(String key, Object value) {
request.setAttribute(key, value);
return this;
}
/**
* [Request作用域] 里获取一个值
*/
@Override
public Object get(String key) {
return request.getAttribute(key);
}
/**
* [Request作用域] 里删除一个值
*/
@Override
public SaStorageForServlet delete(String key) {
request.removeAttribute(key);
return this;
}
}

View File

@ -0,0 +1,4 @@
/**
* Sa-Token对接ServletAPI容器所需要的实现类接口包
*/
package cn.dev33.satoken.servlet;

View File

@ -17,12 +17,6 @@
<description>springboot reactor integrate sa-token</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
</dependency>
<!-- spring-boot-starter (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -34,7 +28,6 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.0</version>
<optional>true</optional>
</dependency>
@ -59,18 +52,16 @@
<optional>true</optional>
</dependency>
<!-- OAuth2.0 (optional) -->
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<optional>true</optional>
<artifactId>sa-token-core</artifactId>
</dependency>
<!-- SSO (optional) -->
<!-- sa-token-spring-boot-autoconfig -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<optional>true</optional>
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
</dependency>
<!-- <dependency>
@ -81,5 +72,17 @@
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.7</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -1,186 +0,0 @@
package cn.dev33.satoken.reactor.spring;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.basic.SaBasicTemplate;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.id.SaIdTemplate;
import cn.dev33.satoken.id.SaIdUtil;
import cn.dev33.satoken.json.SaJsonTemplate;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.log.SaLog;
import cn.dev33.satoken.same.SaSameTemplate;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempInterface;
/**
* 利用spring的自动装配来加载开发者重写的Bean
*
* @author kong
*
*/
@SuppressWarnings("deprecation")
public class SaBeanInject {
/**
* 组件注入
* <p> 为确保 Log 组件正常打印必须将 SaLog SaTokenConfig 率先初始化 </p>
*
* @param saTokenConfig 配置对象
*/
public SaBeanInject(
@Autowired(required = false) SaLog log,
@Autowired(required = false) SaTokenConfig saTokenConfig
){
if(log != null) {
SaManager.setLog(log);
}
if(saTokenConfig != null) {
SaManager.setConfig(saTokenConfig);
}
}
/**
* 注入持久化Bean
*
* @param saTokenDao SaTokenDao对象
*/
@Autowired(required = false)
public void setSaTokenDao(SaTokenDao saTokenDao) {
SaManager.setSaTokenDao(saTokenDao);
}
/**
* 注入权限认证Bean
*
* @param stpInterface StpInterface对象
*/
@Autowired(required = false)
public void setStpInterface(StpInterface stpInterface) {
SaManager.setStpInterface(stpInterface);
}
/**
* 注入上下文Bean
*
* @param saTokenContext SaTokenContext对象
*/
@Autowired(required = false)
public void setSaTokenContext(SaTokenContext saTokenContext) {
SaManager.setSaTokenContext(saTokenContext);
}
/**
* 注入二级上下文Bean
*
* @param saTokenSecondContextCreator 二级上下文创建器
*/
@Autowired(required = false)
public void setSaTokenContext(SaTokenSecondContextCreator saTokenSecondContextCreator) {
SaManager.setSaTokenSecondContext(saTokenSecondContextCreator.create());
}
/**
* 注入侦听器Bean
*
* @param listenerList 侦听器集合
*/
@Autowired(required = false)
public void setSaTokenListener(List<SaTokenListener> listenerList) {
SaTokenEventCenter.registerListenerList(listenerList);
}
/**
* 注入临时令牌验证模块 Bean
*
* @param saTemp saTemp对象
*/
@Autowired(required = false)
public void setSaTemp(SaTempInterface saTemp) {
SaManager.setSaTemp(saTemp);
}
/**
* 注入 Sa-Id-Token 模块 Bean
*
* @param saIdTemplate saIdTemplate对象
*/
@Autowired(required = false)
public void setSaIdTemplate(SaIdTemplate saIdTemplate) {
SaIdUtil.saIdTemplate = saIdTemplate;
}
/**
* 注入 Same-Token 模块 Bean
*
* @param saSameTemplate saSameTemplate对象
*/
@Autowired(required = false)
public void setSaIdTemplate(SaSameTemplate saSameTemplate) {
SaManager.setSaSameTemplate(saSameTemplate);
}
/**
* 注入 Sa-Token Http Basic 认证模块
*
* @param saBasicTemplate saBasicTemplate对象
*/
@Autowired(required = false)
public void setSaBasicTemplate(SaBasicTemplate saBasicTemplate) {
SaBasicUtil.saBasicTemplate = saBasicTemplate;
}
/**
* 注入自定义的 JSON 转换器 Bean
*
* @param saJsonTemplate JSON 转换器
*/
@Autowired(required = false)
public void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) {
SaManager.setSaJsonTemplate(saJsonTemplate);
}
/**
* 注入自定义的 参数签名 Bean
*
* @param saSignTemplate 参数签名 Bean
*/
@Autowired(required = false)
public void setSaSignTemplate(SaSignTemplate saSignTemplate) {
SaManager.setSaSignTemplate(saSignTemplate);
}
/**
* 注入自定义的 StpLogic
* @param stpLogic /
*/
@Autowired(required = false)
public void setStpLogic(StpLogic stpLogic) {
StpUtil.setStpLogic(stpLogic);
}
/**
* 利用自动注入特性获取Spring框架内部使用的路由匹配器
*
* @param pathMatcher 要设置的 pathMatcher
*/
@Autowired(required = false)
@Qualifier("mvcPathMatcher")
public void setPathMatcher(PathMatcher pathMatcher) {
SaPathMatcherHolder.setPathMatcher(pathMatcher);
}
}

View File

@ -1,50 +0,0 @@
package cn.dev33.satoken.reactor.spring;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.json.SaJsonTemplate;
import cn.dev33.satoken.reactor.spring.json.SaJsonTemplateForJackson;
/**
* 注册Sa-Token所需要的Bean
* <p> Bean 的注册与注入应该分开在两个文件中否则在某些场景下会造成循环依赖
* @author kong
*
*/
public class SaBeanRegister {
/**
* 获取配置Bean
*
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token")
public SaTokenConfig getSaTokenConfig() {
return new SaTokenConfig();
}
/**
* 获取容器交互Bean (ThreadLocal版)
*
* @return 容器交互Bean (ThreadLocal版)
*/
@Bean
public SaTokenContext getSaTokenContext() {
return new SaTokenContextForSpringReactor();
}
/**
* 获取 json 转换器 Bean (Jackson版)
*
* @return json 转换器 Bean (Jackson版)
*/
@Bean
public SaJsonTemplate getSaJsonTemplateForJackson() {
return new SaJsonTemplateForJackson();
}
}

View File

@ -1,37 +0,0 @@
package cn.dev33.satoken.reactor.spring;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
/**
*
* @author kong
*
*/
public class SaPathMatcherHolder {
/**
* 路由匹配器
*/
public static PathMatcher pathMatcher;
/**
* 获取路由匹配器
* @return 路由匹配器
*/
public static PathMatcher getPathMatcher() {
if(pathMatcher == null) {
pathMatcher = new AntPathMatcher();
}
return pathMatcher;
}
/**
* 写入路由匹配器
* @param pathMatcher 路由匹配器
*/
public static void setPathMatcher(PathMatcher pathMatcher) {
SaPathMatcherHolder.pathMatcher = pathMatcher;
}
}

View File

@ -1,6 +1,7 @@
package cn.dev33.satoken.reactor.spring;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.spring.SaPathMatcherHolder;
/**
* Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ]

View File

@ -0,0 +1,25 @@
package cn.dev33.satoken.reactor.spring;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
/**
* 注册Sa-Token所需要的Bean
* <p> Bean 的注册与注入应该分开在两个文件中否则在某些场景下会造成循环依赖
* @author kong
*
*/
public class SaTokenContextRegister {
/**
* 获取容器交互Bean (ThreadLocal版)
*
* @return 容器交互Bean (ThreadLocal版)
*/
@Bean
public SaTokenContext getSaTokenContextForSpringReactor() {
return new SaTokenContextForSpringReactor();
}
}

View File

@ -1,53 +0,0 @@
package cn.dev33.satoken.reactor.spring.json;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.exception.SaJsonConvertException;
import cn.dev33.satoken.json.SaJsonTemplate;
import cn.dev33.satoken.reactor.error.SaReactorSpringBootErrorCode;
/**
* JSON 转换器 Jackson 版实现
*
* @author kong
* @since: 2022-4-26
*/
public class SaJsonTemplateForJackson implements SaJsonTemplate {
/**
* 底层 Mapper 对象
*/
public ObjectMapper objectMapper = new ObjectMapper();
/**
* 将任意对象转换为 json 字符串
*
* @param obj 对象
* @return 转换后的 json 字符串
*/
public String toJsonString(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new SaJsonConvertException(e).setCode(SaReactorSpringBootErrorCode.CODE_20203);
}
}
/**
* json 字符串解析为 Map
*/
@Override
public Map<String, Object> parseJsonToMap(String jsonStr) {
try {
@SuppressWarnings("unchecked")
Map<String, Object> map = objectMapper.readValue(jsonStr, Map.class);
return map;
} catch (JsonProcessingException e) {
throw new SaJsonConvertException(e).setCode(SaReactorSpringBootErrorCode.CODE_20204);
}
}
}

View File

@ -1,40 +0,0 @@
package cn.dev33.satoken.reactor.spring.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Template;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
/**
* 注入 Sa-Token-OAuth2 所需要的Bean
*
* @author kong
*
*/
@ConditionalOnClass(SaOAuth2Manager.class)
public class SaOAuth2BeanInject {
/**
* 注入OAuth2配置Bean
*
* @param saOAuth2Config 配置对象
*/
@Autowired(required = false)
public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) {
SaOAuth2Manager.setConfig(saOAuth2Config);
}
/**
* 注入代码模板Bean
*
* @param saOAuth2Template 代码模板Bean
*/
@Autowired(required = false)
public void setSaOAuth2Interface(SaOAuth2Template saOAuth2Template) {
SaOAuth2Util.saOAuth2Template = saOAuth2Template;
}
}

View File

@ -1,28 +0,0 @@
package cn.dev33.satoken.reactor.spring.oauth2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
/**
* 注册 Sa-Token-OAuth2 所需要的Bean
* @author kong
*
*/
@ConditionalOnClass(SaOAuth2Manager.class)
public class SaOAuth2BeanRegister {
/**
* 获取OAuth2配置Bean
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.oauth2")
public SaOAuth2Config getSaOAuth2Config() {
return new SaOAuth2Config();
}
}

View File

@ -1,4 +0,0 @@
/**
* Sa-Token-OAuth2 模块自动化配置只有引入了Sa-Token-OAuth2模块后此包下的代码才会开始工作
*/
package cn.dev33.satoken.reactor.spring.oauth2;

View File

@ -1,40 +0,0 @@
package cn.dev33.satoken.reactor.spring.sso;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.SaSsoTemplate;
import cn.dev33.satoken.sso.SaSsoUtil;
/**
* 注入 Sa-Token-SSO 所需要的Bean
*
* @author kong
*
*/
@ConditionalOnClass(SaSsoManager.class)
public class SaSsoBeanInject {
/**
* 注入 Sa-Token-SSO 配置Bean
*
* @param saSsoConfig 配置对象
*/
@Autowired(required = false)
public void setSaOAuth2Config(SaSsoConfig saSsoConfig) {
SaSsoManager.setConfig(saSsoConfig);
}
/**
* 注入 Sa-Token-SSO 单点登录模块 Bean
*
* @param saSsoTemplate saSsoTemplate对象
*/
@Autowired(required = false)
public void setSaSsoTemplate(SaSsoTemplate saSsoTemplate) {
SaSsoUtil.ssoTemplate = saSsoTemplate;
}
}

View File

@ -1,28 +0,0 @@
package cn.dev33.satoken.reactor.spring.sso;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
/**
* 注册 Sa-Token-SSO 所需要的Bean
* @author kong
*
*/
@ConditionalOnClass(SaSsoManager.class)
public class SaSsoBeanRegister {
/**
* 获取 SSO 配置Bean
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.sso")
public SaSsoConfig getSaSsoConfig() {
return new SaSsoConfig();
}
}

View File

@ -1,4 +0,0 @@
/**
* Sa-Token-SSO 模块自动化配置只有引入了 sa-token-sso 模块后此包下的代码才会开始工作
*/
package cn.dev33.satoken.reactor.spring.sso;

View File

@ -1,7 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dev33.satoken.reactor.spring.SaBeanRegister,\
cn.dev33.satoken.reactor.spring.SaBeanInject,\
cn.dev33.satoken.reactor.spring.sso.SaSsoBeanRegister,\
cn.dev33.satoken.reactor.spring.sso.SaSsoBeanInject,\
cn.dev33.satoken.reactor.spring.oauth2.SaOAuth2BeanRegister,\
cn.dev33.satoken.reactor.spring.oauth2.SaOAuth2BeanInject
cn.dev33.satoken.reactor.spring.SaTokenContextRegister

View File

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

View File

@ -0,0 +1,118 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-starter</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<name>sa-token-reactor-spring-boot3-starter</name>
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<description>springboot3 reactor integrate sa-token</description>
<properties>
<springboot3.version>3.0.1</springboot3.version>
</properties>
<dependencies>
<!-- spring-boot-starter (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- spring-web (optional) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<!--<version>5.3.7</version>-->
<optional>true</optional>
</dependency>
<!-- reactor-core (optional) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<!-- jackson-databind (optional) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<!-- config -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
</dependency>
<!-- sa-token-spring-boot-autoconfig -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${springboot.version}</version>
</dependency> -->
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot3.version}</version>
</dependency>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.5.1</version>
</dependency>
<!-- jackson-databind (optional) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
<!-- config (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${springboot3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,49 @@
package cn.dev33.satoken.reactor.context;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Reactor上下文操作 [异步]
* @author kong
*
*/
public class SaReactorHolder {
/**
* key
*/
public static final Class<ServerWebExchange> CONTEXT_KEY = ServerWebExchange.class;
/**
* chain_key
*/
public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY";
/**
* 获取上下文对象
* @return see note
*/
public static Mono<ServerWebExchange> getContext() {
// 从全局 Mono<Context> 获取
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
}
/**
* 获取上下文对象, 并设置到同步上下文中
* @return see note
*/
public static Mono<ServerWebExchange> getContextAndSetSync() {
// 从全局 Mono<Context> 获取
return Mono.deferContextual(Mono::just).map(ctx -> {
// 设置到sync中
SaReactorSyncHolder.setContext(ctx.get(CONTEXT_KEY));
return ctx.get(CONTEXT_KEY);
}).doFinally(r->{
// 从sync中清除
SaReactorSyncHolder.clearContext();
});
}
}

View File

@ -0,0 +1,63 @@
package cn.dev33.satoken.reactor.context;
import org.springframework.web.server.ServerWebExchange;
import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage;
import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage.Box;
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.fun.SaFunction;
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
import cn.dev33.satoken.reactor.model.SaStorageForReactor;
/**
* Reactor上下文操作 [同步]
* @author kong
*
*/
public class SaReactorSyncHolder {
/**
* 写入上下文对象
* @param exchange see note
*/
public static void setContext(ServerWebExchange exchange) {
SaRequest request = new SaRequestForReactor(exchange.getRequest());
SaResponse response = new SaResponseForReactor(exchange.getResponse());
SaStorage storage = new SaStorageForReactor(exchange);
SaTokenContextForThreadLocalStorage.setBox(request, response, storage);
}
/**
* 获取上下文对象
* @return see note
*/
public static ServerWebExchange getContext() {
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
return (ServerWebExchange)box.getStorage().getSource();
}
/**
* 清除上下文对象
*/
public static void clearContext() {
SaTokenContextForThreadLocalStorage.clearBox();
}
/**
* 写入上下文对象, 并在执行函数后将其清除
* @param exchange see note
* @param fun see note
*/
public static void setContext(ServerWebExchange exchange, SaFunction fun) {
try {
setContext(exchange);
fun.run();
} finally {
clearContext();
}
}
}

View File

@ -0,0 +1,20 @@
package cn.dev33.satoken.reactor.error;
/**
* 定义 sa-token-reactor-spring-boot-starter 所有异常细分状态码
*
* @author kong
* @since: 2022-10-30
*/
public interface SaReactorSpringBootErrorCode {
/** 对象转 JSON 字符串失败 */
public static final int CODE_20203 = 20203;
/** JSON 字符串转 Map 失败 */
public static final int CODE_20204 = 20204;
/** 默认的 Filter 异常处理函数 */
public static final int CODE_20205 = 20205;
}

View File

@ -0,0 +1,203 @@
package cn.dev33.satoken.reactor.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.error.SaReactorSpringBootErrorCode;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.util.SaTokenConsts;
import reactor.core.publisher.Mono;
/**
* Reactor全局过滤器
* @author kong
*
*/
@Order(SaTokenConsts.ASSEMBLY_ORDER)
public class SaReactorFilter implements WebFilter {
// ------------------------ 设置此过滤器 拦截 & 放行 的路由
/**
* 拦截路由
*/
private List<String> includeList = new ArrayList<>();
/**
* 放行路由
*/
private List<String> excludeList = new ArrayList<>();
/**
* 添加 [拦截路由]
* @param paths 路由
* @return 对象自身
*/
public SaReactorFilter addInclude(String... paths) {
includeList.addAll(Arrays.asList(paths));
return this;
}
/**
* 添加 [放行路由]
* @param paths 路由
* @return 对象自身
*/
public SaReactorFilter addExclude(String... paths) {
excludeList.addAll(Arrays.asList(paths));
return this;
}
/**
* 写入 [拦截路由] 集合
* @param pathList 路由集合
* @return 对象自身
*/
public SaReactorFilter setIncludeList(List<String> pathList) {
includeList = pathList;
return this;
}
/**
* 写入 [放行路由] 集合
* @param pathList 路由集合
* @return 对象自身
*/
public SaReactorFilter setExcludeList(List<String> pathList) {
excludeList = pathList;
return this;
}
/**
* 获取 [拦截路由] 集合
* @return see note
*/
public List<String> getIncludeList() {
return includeList;
}
/**
* 获取 [放行路由] 集合
* @return see note
*/
public List<String> getExcludeList() {
return excludeList;
}
// ------------------------ 钩子函数
/**
* 认证函数每次请求执行
*/
public SaFilterAuthStrategy auth = r -> {};
/**
* 异常处理函数每次[认证函数]发生异常时执行此函数
*/
public SaFilterErrorStrategy error = e -> {
throw new SaTokenException(e).setCode(SaReactorSpringBootErrorCode.CODE_20205);
};
/**
* 前置函数在每次[认证函数]之前执行
*/
public SaFilterAuthStrategy beforeAuth = r -> {};
/**
* 写入[认证函数]: 每次请求执行
* @param auth see note
* @return 对象自身
*/
public SaReactorFilter setAuth(SaFilterAuthStrategy auth) {
this.auth = auth;
return this;
}
/**
* 写入[异常处理函数]每次[认证函数]发生异常时执行此函数
* @param error see note
* @return 对象自身
*/
public SaReactorFilter setError(SaFilterErrorStrategy error) {
this.error = error;
return this;
}
/**
* 写入[前置函数]在每次[认证函数]之前执行
* @param beforeAuth see note
* @return 对象自身
*/
public SaReactorFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) {
this.beforeAuth = beforeAuth;
return this;
}
// ------------------------ filter
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 写入WebFilterChain对象
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);
// ---------- 全局认证处理
try {
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);
// 执行全局过滤器
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
beforeAuth.run(null);
auth.run(null);
});
} catch (StopMatchException e) {
} catch (Throwable e) {
// 1. 获取异常处理策略结果
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
// 2. 写入输出流
if(exchange.getResponse().getHeaders().getFirst("Content-Type") == null) {
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
}
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
} finally {
// 清除上下文
SaReactorSyncHolder.clearContext();
}
// ---------- 执行
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);
// 执行
return chain.filter(exchange).contextWrite(ctx -> {
// 写入全局上下文 (异步)
ctx = ctx.put(SaReactorHolder.CONTEXT_KEY, exchange);
return ctx;
}).doFinally(r -> {
// 清除上下文
SaReactorSyncHolder.clearContext();
});
}
}

View File

@ -0,0 +1,113 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Request for Reactor
* @author kong
*
*/
public class SaRequestForReactor implements SaRequest {
/**
* 底层Request对象
*/
protected ServerHttpRequest request;
/**
* 实例化
* @param request request对象
*/
public SaRequestForReactor(ServerHttpRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return request;
}
/**
* [请求体] 里获取一个值
*/
@Override
public String getParam(String name) {
return request.getQueryParams().getFirst(name);
}
/**
* [请求头] 里获取一个值
*/
@Override
public String getHeader(String name) {
return request.getHeaders().getFirst(name);
}
/**
* [Cookie作用域] 里获取一个值
*/
@Override
public String getCookieValue(String name) {
HttpCookie cookie = request.getCookies().getFirst(name);
if(cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestPath() {
return request.getURI().getPath();
}
/**
* 返回当前请求的urlhttp://xxx.com/test
* @return see note
*/
public String getUrl() {
String currDomain = SaManager.getConfig().getCurrDomain();
if(SaFoxUtil.isEmpty(currDomain) == false) {
return currDomain + this.getRequestPath();
}
return request.getURI().toString();
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return request.getMethod().name();
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY);
ServerHttpRequest newRequest = request.mutate().path(path).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}
}

View File

@ -0,0 +1,77 @@
package cn.dev33.satoken.reactor.model;
import java.net.URI;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import cn.dev33.satoken.context.model.SaResponse;
/**
* Response for Reactor
* @author kong
*
*/
public class SaResponseForReactor implements SaResponse {
/**
* 底层Response对象
*/
protected ServerHttpResponse response;
/**
* 实例化
* @param response response对象
*/
public SaResponseForReactor(ServerHttpResponse response) {
this.response = response;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return response;
}
/**
* 设置响应状态码
*/
@Override
public SaResponse setStatus(int sc) {
response.setStatusCode(HttpStatus.valueOf(sc));
return this;
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
response.getHeaders().set(name, value);
return this;
}
/**
* 在响应头里添加一个值
* @param name 名字
* @param value
* @return 对象自身
*/
public SaResponse addHeader(String name, String value) {
response.getHeaders().add(name, value);
return this;
}
/**
* 重定向
*/
@Override
public Object redirect(String url) {
response.setStatusCode(HttpStatus.FOUND);
response.getHeaders().setLocation(URI.create(url));
return null;
}
}

View File

@ -0,0 +1,61 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.web.server.ServerWebExchange;
import cn.dev33.satoken.context.model.SaStorage;
/**
* Storage for Reactor
* @author kong
*
*/
public class SaStorageForReactor implements SaStorage {
/**
* 底层Request对象
*/
protected ServerWebExchange exchange;
/**
* 实例化
* @param exchange exchange对象
*/
public SaStorageForReactor(ServerWebExchange exchange) {
this.exchange = exchange;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return exchange;
}
/**
* [Request作用域] 里写入一个值
*/
@Override
public SaStorageForReactor set(String key, Object value) {
exchange.getAttributes().put(key, value);
return this;
}
/**
* [Request作用域] 里获取一个值
*/
@Override
public Object get(String key) {
return exchange.getAttributes().get(key);
}
/**
* [Request作用域] 里删除一个值
*/
@Override
public SaStorageForReactor delete(String key) {
exchange.getAttributes().remove(key);
return this;
}
}

View File

@ -0,0 +1,4 @@
/**
* sa-token集成Reactor响应式编程的各个组件
*/
package cn.dev33.satoken.reactor;

View File

@ -0,0 +1,22 @@
package cn.dev33.satoken.reactor.spring;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.spring.SaPathMatcherHolder;
/**
* Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ]
*
* @author kong
*
*/
public class SaTokenContextForSpringReactor extends SaTokenContextForThreadLocal {
/**
* 重写路由匹配方法
*/
@Override
public boolean matchPath(String pattern, String path) {
return SaPathMatcherHolder.getPathMatcher().match(pattern, path);
}
}

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