refactor: 重构所有 starter 组件的 SaTokenContext 上下文读写策略
This commit is contained in:
@@ -22,6 +22,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.config.SaTokenConfigFactory;
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
|
||||
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
|
||||
import cn.dev33.satoken.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
|
||||
@@ -155,6 +156,13 @@ public class SaManager {
|
||||
SaTokenEventCenter.doRegisterComponent("SaTokenContext", saTokenContext);
|
||||
}
|
||||
public static SaTokenContext getSaTokenContext() {
|
||||
if (saTokenContext == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (saTokenContext == null) {
|
||||
SaManager.saTokenContext = new SaTokenContextForThreadLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
return saTokenContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaTokenContext getContext() {
|
||||
return SaManager.getSaTokenContextOrSecond();
|
||||
return SaManager.getSaTokenContext();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaRequest getRequest() {
|
||||
return SaManager.getSaTokenContextOrSecond().getRequest();
|
||||
return SaManager.getSaTokenContext().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaResponse getResponse() {
|
||||
return SaManager.getSaTokenContextOrSecond().getResponse();
|
||||
return SaManager.getSaTokenContext().getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +66,7 @@ public class SaHolder {
|
||||
* @return /
|
||||
*/
|
||||
public static SaStorage getStorage() {
|
||||
return SaManager.getSaTokenContextOrSecond().getStorage();
|
||||
return SaManager.getSaTokenContext().getStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
package cn.dev33.satoken.context;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器
|
||||
@@ -53,20 +53,6 @@ public interface SaTokenContext {
|
||||
*/
|
||||
SaStorage getStorage();
|
||||
|
||||
/**
|
||||
* 判断:指定路由匹配符是否可以匹配成功指定路径
|
||||
* <pre>
|
||||
* 判断规则由底层 web 框架决定,例如在 springboot 中:
|
||||
* - matchPath("/user/*", "/user/login") 返回: true
|
||||
* - matchPath("/user/*", "/article/edit") 返回: false
|
||||
* </pre>
|
||||
*
|
||||
* @param pattern 路由匹配符
|
||||
* @param path 需要匹配的路径
|
||||
* @return /
|
||||
*/
|
||||
boolean matchPath(String pattern, String path);
|
||||
|
||||
/**
|
||||
* 判断:在本次请求中,此上下文是否可用。
|
||||
* <p> 例如在部分 rpc 调用时, 一级上下文会返回 false,这时候框架就会选择使用二级上下文来处理请求 </p>
|
||||
|
||||
@@ -59,9 +59,4 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new SaTokenContextException(ERROR_MESSAGE).setCode(SaErrorCode.CODE_10001);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,11 +49,6 @@ public class SaTokenContextForThreadLocal implements SaTokenContext {
|
||||
return SaTokenContextForThreadLocalStorage.getStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SaTokenContextForThreadLocalStorage.getBox() != null;
|
||||
|
||||
@@ -227,4 +227,9 @@ public interface SaErrorCode {
|
||||
/** API Key 不属于指定用户 */
|
||||
int CODE_12312 = 12312;
|
||||
|
||||
// ------------
|
||||
|
||||
/** 未实现具体的路由匹配策略 */
|
||||
int CODE_12401 = 12401;
|
||||
|
||||
}
|
||||
|
||||
+10
-12
@@ -13,23 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.error;
|
||||
package cn.dev33.satoken.fun;
|
||||
|
||||
/**
|
||||
* 定义 sa-token-reactor-spring-boot-starter 所有异常细分状态码
|
||||
* 无形参、有返回值(泛型)的函数式接口,方便开发者进行 lambda 表达式风格调用
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.33.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public interface SaReactorSpringBootErrorCode {
|
||||
@FunctionalInterface
|
||||
public interface SaRetGenericFunction<T> {
|
||||
|
||||
/** 对象转 JSON 字符串失败 */
|
||||
int CODE_20203 = 20203;
|
||||
|
||||
/** JSON 字符串转 Map 失败 */
|
||||
int CODE_20204 = 20204;
|
||||
|
||||
/** 默认的 Filter 异常处理函数 */
|
||||
int CODE_20205 = 20205;
|
||||
/**
|
||||
* 执行的方法
|
||||
* @return 返回值
|
||||
*/
|
||||
T run();
|
||||
|
||||
}
|
||||
+10
-13
@@ -13,23 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.error;
|
||||
package cn.dev33.satoken.fun.strategy;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* 定义 sa-token-reactor3-spring-boot-starter 所有异常细分状态码
|
||||
* 函数式接口:路由匹配策略
|
||||
*
|
||||
* <p> 参数:pattern, path </p>
|
||||
* <p> 返回:是否匹配 </p>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public interface SaReactorSpringBootErrorCode {
|
||||
|
||||
/** 对象转 JSON 字符串失败 */
|
||||
int CODE_20203 = 20203;
|
||||
|
||||
/** JSON 字符串转 Map 失败 */
|
||||
int CODE_20204 = 20204;
|
||||
|
||||
/** 默认的 Filter 异常处理函数 */
|
||||
int CODE_20205 = 20205;
|
||||
@FunctionalInterface
|
||||
public interface SaRouteMatchFunction extends BiFunction<String, String, Boolean> {
|
||||
|
||||
}
|
||||
@@ -15,15 +15,15 @@
|
||||
*/
|
||||
package cn.dev33.satoken.router;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.fun.SaFunction;
|
||||
import cn.dev33.satoken.fun.SaParamFunction;
|
||||
import cn.dev33.satoken.fun.SaParamRetFunction;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 路由匹配操作工具类
|
||||
@@ -55,7 +55,7 @@ public class SaRouter {
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
public static boolean isMatch(String pattern, String path) {
|
||||
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
|
||||
return SaStrategy.instance.routeMatcher.apply(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package cn.dev33.satoken.strategy;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.NotImplException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.fun.strategy.*;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
@@ -175,6 +177,13 @@ public final class SaStrategy {
|
||||
return new StpLogic(loginType);
|
||||
};
|
||||
|
||||
/**
|
||||
* 路由匹配策略
|
||||
*/
|
||||
public SaRouteMatchFunction routeMatcher = (pattern, path) -> {
|
||||
throw new NotImplException("未实现具体路由匹配策略").setCode(SaErrorCode.CODE_12401);
|
||||
};
|
||||
|
||||
|
||||
// ----------------------- 重写策略 set连缀风格
|
||||
|
||||
|
||||
@@ -194,7 +194,17 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* 防火墙校验过滤器的注册顺序
|
||||
*/
|
||||
public static final int FIREWALL_CHECK_FILTER_ORDER = -1000;
|
||||
public static final int FIREWALL_CHECK_FILTER_ORDER = -102;
|
||||
|
||||
/**
|
||||
* 跨域处理过滤器的注册顺序
|
||||
*/
|
||||
public static final int CORS_FILTER_ORDER = -103;
|
||||
|
||||
/**
|
||||
* 上下文过滤器的注册顺序
|
||||
*/
|
||||
public static final int SA_TOKEN_CONTEXT_FILTER_ORDER = -104;
|
||||
|
||||
/**
|
||||
* Content-Type key
|
||||
|
||||
@@ -17,4 +17,5 @@ public class SaTokenDemoApp {
|
||||
Solon.start(SaTokenDemoApp.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
|
||||
import cn.dev33.satoken.spring.SpringMVCUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -19,6 +20,8 @@ public class TestController {
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("------------进来了");
|
||||
System.out.println(SpringMVCUtil.getRequest());
|
||||
System.out.println(SaTokenContextUtil.getRequest());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
|
||||
import cn.dev33.satoken.spring.SpringMVCUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -17,9 +18,12 @@ public class Test2Controller {
|
||||
@RequestMapping("/test")
|
||||
public SaResult test2() {
|
||||
|
||||
StpUtil.login(30003);
|
||||
System.out.println(StpUtil.getSession().timeout());
|
||||
System.out.println(StpUtil.getStpLogic().getTokenSession(false));
|
||||
System.out.println(SpringMVCUtil.getRequest());
|
||||
System.out.println(SaTokenContextUtil.getRequest());
|
||||
|
||||
// StpUtil.login(30003);
|
||||
// System.out.println(StpUtil.getSession().timeout());
|
||||
// System.out.println(StpUtil.getStpLogic().getTokenSession(false));
|
||||
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
<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>
|
||||
@@ -38,17 +34,10 @@
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
|
||||
<!-- Sa-Token 整合 Redis -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- sa-token整合redis (使用jackson序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
|
||||
+3
-5
@@ -1,12 +1,10 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
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 click33
|
||||
@@ -33,7 +31,7 @@ public class SaTokenConfigure {
|
||||
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
+7
-9
@@ -1,5 +1,8 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
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.http.MediaType;
|
||||
@@ -8,10 +11,6 @@ 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 {
|
||||
|
||||
@@ -23,12 +22,11 @@ public class DefineRoutes {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> getRoutes() {
|
||||
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
|
||||
// 测试打印
|
||||
return SaReactorSyncHolder.setContext(req.exchange(), () -> {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
|
||||
// 返回结果
|
||||
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj);
|
||||
SaResult res = SaResult.data(StpUtil.getTokenInfo());
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+5
-38
@@ -1,52 +1,19 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
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;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ResponseBody
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
public SaResult handlerException(Exception e) {
|
||||
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;
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+74
-39
@@ -1,17 +1,19 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorHolder;
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
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 org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author click33
|
||||
@@ -21,60 +23,93 @@ import reactor.core.publisher.Mono;
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试登录接口 [同步模式], 浏览器访问: http://localhost:8081/test/login
|
||||
@Autowired
|
||||
UserService userService;
|
||||
|
||||
// 登录测试:Controller 里调用 Sa-Token API --- http://localhost:8081/test/login
|
||||
@RequestMapping("login")
|
||||
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
|
||||
public Mono<SaResult> login(@RequestParam(defaultValue="10001") String id) {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
StpUtil.login(id);
|
||||
return AjaxJson.getSuccess("登录成功");
|
||||
return SaResult.ok("登录成功");
|
||||
});
|
||||
}
|
||||
|
||||
// API测试 [同步模式], 浏览器访问: http://localhost:8081/test/isLogin
|
||||
// API测试:手动设置上下文、try-finally 形式 --- http://localhost:8081/test/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public AjaxJson isLogin() {
|
||||
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
|
||||
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
|
||||
public SaResult isLogin(ServerWebExchange exchange) {
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
} finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
// API测试 [异步模式], 浏览器访问: http://localhost:8081/test/isLogin2
|
||||
// API测试:手动设置上下文、lambda 表达式形式 --- 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);
|
||||
public SaResult isLogin2(ServerWebExchange exchange) {
|
||||
SaResult res = SaReactorSyncHolder.setContext(exchange, ()->{
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
return SaResult.data(res);
|
||||
}
|
||||
|
||||
// API测试 [异步模式, 同一线程], 浏览器访问: http://localhost:8081/test/isLogin3
|
||||
// API测试:自动设置上下文、lambda 表达式形式 --- 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());
|
||||
public Mono<SaResult> isLogin3() {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
userService.isLogin();
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
}
|
||||
|
||||
// API测试 [异步模式, 不同线程], 浏览器访问: http://localhost:8081/test/isLogin4
|
||||
// API测试:自动设置上下文、调用 userService Mono 方法 --- http://localhost:8081/test/isLogin4
|
||||
@RequestMapping("isLogin4")
|
||||
public Mono<AjaxJson> isLogin4() {
|
||||
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
|
||||
public Mono<SaResult> isLogin4() {
|
||||
return userService.findUserIdByNamePwd("ZhangSan", "123456").flatMap(userId -> {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
StpUtil.login(userId);
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// API测试:切换线程、复杂嵌套调用 --- http://localhost:8081/test/isLogin5
|
||||
@RequestMapping("isLogin5")
|
||||
public Mono<SaResult> isLogin5() {
|
||||
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());
|
||||
// 要点:在流里调用 Sa-Token API 之前,必须用 SaReactorHolder.sync( () -> {} ) 进行包裹
|
||||
return Mono.delay(Duration.ofSeconds(1))
|
||||
.doOnNext(r-> System.out.println("线程id-----" + Thread.currentThread().getId()))
|
||||
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
|
||||
.map(r-> userService.findUserIdByNamePwd("ZhangSan", "123456"))
|
||||
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
|
||||
.flatMap(isLogin -> {
|
||||
System.out.println("是否登录 " + isLogin);
|
||||
return SaReactorHolder.sync(() -> {
|
||||
System.out.println("是否登录 " + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// API测试:使用上下文无关的API --- http://localhost:8081/test/isLogin6
|
||||
@RequestMapping("isLogin6")
|
||||
public SaResult isLogin6(@CookieValue("satoken") String satoken) {
|
||||
System.out.println("token 为:" + satoken);
|
||||
System.out.println("登录人:" + StpUtil.getLoginIdByToken(satoken));
|
||||
return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken));
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: 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());
|
||||
public SaResult test() {
|
||||
System.out.println("线程id------- " + Thread.currentThread().getId());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 模拟 Service 方法
|
||||
* @author click33
|
||||
* @since 2025/4/6
|
||||
*/
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
public boolean isLogin() {
|
||||
System.out.println("UserService 里调用 API 测试,是否登录:" + StpUtil.isLogin());
|
||||
return StpUtil.isLogin();
|
||||
}
|
||||
|
||||
public Mono<Long> findUserIdByNamePwd(String name, String pwd) {
|
||||
// ...
|
||||
return Mono.just(10001L);
|
||||
}
|
||||
|
||||
}
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
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=ok,false=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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
<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>
|
||||
@@ -38,17 +34,10 @@
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
|
||||
<!-- Sa-Token 整合 Redis -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- sa-token整合redis (使用jackson序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
|
||||
+1
-2
@@ -1,10 +1,9 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* Sa-Token整合webflux 示例
|
||||
* @author click33
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
|
||||
import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class SaTokenConfigure {
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
e.printStackTrace();
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
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.http.MediaType;
|
||||
@@ -8,10 +11,6 @@ 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 {
|
||||
|
||||
@@ -23,12 +22,11 @@ public class DefineRoutes {
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> getRoutes() {
|
||||
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
|
||||
// 测试打印
|
||||
return SaReactorSyncHolder.setContext(req.exchange(), () -> {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
|
||||
// 返回结果
|
||||
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj);
|
||||
SaResult res = SaResult.data(StpUtil.getTokenInfo());
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,19 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
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;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ResponseBody
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
public SaResult handlerException(Exception e) {
|
||||
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;
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorHolder;
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
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.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -19,60 +23,93 @@ import java.time.Duration;
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试登录接口 [同步模式], 浏览器访问: http://localhost:8081/test/login
|
||||
@Autowired
|
||||
UserService userService;
|
||||
|
||||
// 登录测试:Controller 里调用 Sa-Token API --- http://localhost:8081/test/login
|
||||
@RequestMapping("login")
|
||||
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
|
||||
public Mono<SaResult> login(@RequestParam(defaultValue="10001") String id) {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
StpUtil.login(id);
|
||||
return AjaxJson.getSuccess("登录成功");
|
||||
return SaResult.ok("登录成功");
|
||||
});
|
||||
}
|
||||
|
||||
// API测试 [同步模式], 浏览器访问: http://localhost:8081/test/isLogin
|
||||
// API测试:手动设置上下文、try-finally 形式 --- http://localhost:8081/test/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public AjaxJson isLogin() {
|
||||
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
|
||||
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
|
||||
public SaResult isLogin(ServerWebExchange exchange) {
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
} finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
// API测试 [异步模式], 浏览器访问: http://localhost:8081/test/isLogin2
|
||||
// API测试:手动设置上下文、lambda 表达式形式 --- 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);
|
||||
public SaResult isLogin2(ServerWebExchange exchange) {
|
||||
SaResult res = SaReactorSyncHolder.setContext(exchange, ()->{
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
return SaResult.data(res);
|
||||
}
|
||||
|
||||
// API测试 [异步模式, 同一线程], 浏览器访问: http://localhost:8081/test/isLogin3
|
||||
// API测试:自动设置上下文、lambda 表达式形式 --- 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());
|
||||
public Mono<SaResult> isLogin3() {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
userService.isLogin();
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
}
|
||||
|
||||
// API测试 [异步模式, 不同线程], 浏览器访问: http://localhost:8081/test/isLogin4
|
||||
// API测试:自动设置上下文、调用 userService Mono 方法 --- http://localhost:8081/test/isLogin4
|
||||
@RequestMapping("isLogin4")
|
||||
public Mono<AjaxJson> isLogin4() {
|
||||
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
|
||||
public Mono<SaResult> isLogin4() {
|
||||
return userService.findUserIdByNamePwd("ZhangSan", "123456").flatMap(userId -> {
|
||||
return SaReactorHolder.sync(() -> {
|
||||
StpUtil.login(userId);
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// API测试:切换线程、复杂嵌套调用 --- http://localhost:8081/test/isLogin5
|
||||
@RequestMapping("isLogin5")
|
||||
public Mono<SaResult> isLogin5() {
|
||||
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());
|
||||
// 要点:在流里调用 Sa-Token API 之前,必须用 SaReactorHolder.sync( () -> {} ) 进行包裹
|
||||
return Mono.delay(Duration.ofSeconds(1))
|
||||
.doOnNext(r-> System.out.println("线程id-----" + Thread.currentThread().getId()))
|
||||
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
|
||||
.map(r-> userService.findUserIdByNamePwd("ZhangSan", "123456"))
|
||||
.map(r-> SaReactorHolder.sync( () -> userService.isLogin() ))
|
||||
.flatMap(isLogin -> {
|
||||
System.out.println("是否登录 " + isLogin);
|
||||
return SaReactorHolder.sync(() -> {
|
||||
System.out.println("是否登录 " + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// API测试:使用上下文无关的API --- http://localhost:8081/test/isLogin6
|
||||
@RequestMapping("isLogin6")
|
||||
public SaResult isLogin6(@CookieValue("satoken") String satoken) {
|
||||
System.out.println("token 为:" + satoken);
|
||||
System.out.println("登录人:" + StpUtil.getLoginIdByToken(satoken));
|
||||
return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken));
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: 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());
|
||||
public SaResult test() {
|
||||
System.out.println("线程id------- " + Thread.currentThread().getId());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 模拟 Service 方法
|
||||
* @author click33
|
||||
* @since 2025/4/6
|
||||
*/
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
public boolean isLogin() {
|
||||
System.out.println("UserService 里调用 API 测试,是否登录:" + StpUtil.isLogin());
|
||||
return StpUtil.isLogin();
|
||||
}
|
||||
|
||||
public Mono<Long> findUserIdByNamePwd(String name, String pwd) {
|
||||
// ...
|
||||
return Mono.just(10001L);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
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=ok,false=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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -17,7 +17,8 @@
|
||||
<!-- 统一定义依赖版本号 -->
|
||||
<springboot.version>2.7.18</springboot.version>
|
||||
<springboot3.version>3.4.3</springboot3.version>
|
||||
<reactor-core.version>3.1.4.RELEASE</reactor-core.version>
|
||||
<spring-web.low.version>5.3.39</spring-web.low.version>
|
||||
<reactor-core.version>3.7.4</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>
|
||||
|
||||
+1
-8
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package cn.dev33.satoken.context.dubbo;
|
||||
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
|
||||
import cn.dev33.satoken.context.dubbo.model.SaRequestForDubbo;
|
||||
import cn.dev33.satoken.context.dubbo.model.SaResponseForDubbo;
|
||||
import cn.dev33.satoken.context.dubbo.model.SaStorageForDubbo;
|
||||
@@ -24,7 +22,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.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.exception.ApiDisabledException;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
|
||||
/**
|
||||
* Sa-Token 二级上下文 [ Dubbo版本 ]
|
||||
@@ -49,11 +47,6 @@ public class SaTokenSecondContextForDubbo implements SaTokenSecondContext {
|
||||
return new SaStorageForDubbo(RpcContext.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new ApiDisabledException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return RpcContext.getContext() != null;
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public class SaTokenDubboConsumerFilter implements Filter {
|
||||
}
|
||||
|
||||
// 2、调用前,向下传递会话Token
|
||||
if(SaManager.getSaTokenContextOrSecond() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
if(SaManager.getSaTokenContext() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
RpcContext.getContext().setAttachment(SaTokenConsts.JUST_CREATED, StpUtil.getTokenValueNotCut());
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -22,7 +22,6 @@ 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.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.exception.ApiDisabledException;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
|
||||
/**
|
||||
@@ -48,11 +47,6 @@ public class SaTokenSecondContextForDubbo3 implements SaTokenSecondContext {
|
||||
return new SaStorageForDubbo3(RpcContext.getServiceContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new ApiDisabledException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return RpcContext.getServiceContext() != null;
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class SaTokenDubbo3ConsumerFilter implements Filter {
|
||||
}
|
||||
|
||||
// 1. 调用前,向下传递会话Token
|
||||
if(SaManager.getSaTokenContextOrSecond() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
if(SaManager.getSaTokenContext() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
RpcContext.getServiceContext().setAttachment(SaTokenConsts.JUST_CREATED, StpUtil.getTokenValueNotCut());
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -23,7 +23,6 @@ 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.context.second.SaTokenSecondContext;
|
||||
import cn.dev33.satoken.exception.ApiDisabledException;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文 [grpc版本]
|
||||
@@ -48,11 +47,6 @@ public class SaTokenSecondContextForGrpc implements SaTokenSecondContext {
|
||||
return new SaStorageForGrpc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
throw new ApiDisabledException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SaTokenGrpcContext.isNotNull();
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ public class SaTokenGrpcClientInterceptor implements ClientInterceptor, Ordered
|
||||
// 调用前,传递会话Token
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
if (SaFoxUtil.isNotEmpty(tokenValue)
|
||||
&& SaManager.getSaTokenContextOrSecond() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
&& SaManager.getSaTokenContext() != SaTokenContextDefaultImpl.defaultContext) {
|
||||
headers.put(GrpcContextConstants.SA_JUST_CREATED_NOT_PREFIX, tokenValue);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -188,7 +188,7 @@ public class SaRequestForServlet implements SaRequest {
|
||||
@Override
|
||||
public Object forward(String path) {
|
||||
try {
|
||||
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContextOrSecond().getResponse().getSource();
|
||||
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContext().getResponse().getSource();
|
||||
request.getRequestDispatcher(path).forward(request, response);
|
||||
return null;
|
||||
} catch (ServletException | IOException e) {
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.servlet.util;
|
||||
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Jakarta Servlet 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaJakartaServletOperateUtil {
|
||||
|
||||
/**
|
||||
* 写入结果到输出流
|
||||
* @param response /
|
||||
* @param result /
|
||||
*/
|
||||
public static void writeResult(ServletResponse response, String result) throws IOException {
|
||||
// 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(response.getContentType() == null) {
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
response.getWriter().print(result);
|
||||
response.getWriter().flush();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.servlet.util;
|
||||
|
||||
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.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文读写工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTokenContextUtil {
|
||||
|
||||
/**
|
||||
* 写入当前上下文
|
||||
* @param request /
|
||||
* @param response /
|
||||
*/
|
||||
public static void setContext(HttpServletRequest request, HttpServletResponse response) {
|
||||
SaRequest req = new SaRequestForServlet(request);
|
||||
SaResponse res = new SaResponseForServlet(response);
|
||||
SaStorage stg = new SaStorageForServlet(request);
|
||||
SaTokenContextForThreadLocalStorage.setBox(req, res, stg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前上下文
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaTokenContextForThreadLocalStorage.clearBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入上下文对象, 并在执行函数后将其清除
|
||||
* @param request /
|
||||
* @param response /
|
||||
* @param fun /
|
||||
*/
|
||||
public static void setContext(HttpServletRequest request, HttpServletResponse response, SaFunction fun) {
|
||||
try {
|
||||
setContext(request, response);
|
||||
fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Box
|
||||
* @return /
|
||||
*/
|
||||
public static Box getBox() {
|
||||
return SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Request
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (HttpServletRequest) box.getRequest().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Response
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (HttpServletResponse) box.getResponse().getSource();
|
||||
}
|
||||
|
||||
}
|
||||
+9
-8
@@ -22,12 +22,21 @@ import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import io.jboot.web.controller.JbootControllerContext;
|
||||
|
||||
/**
|
||||
* Sa-Token 上线文处理器 [Jboot 版本实现]
|
||||
*/
|
||||
public class SaTokenContextForJboot implements SaTokenContext {
|
||||
|
||||
public SaTokenContextForJboot() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的Request对象
|
||||
*/
|
||||
@@ -52,14 +61,6 @@ public class SaTokenContextForJboot implements SaTokenContext {
|
||||
return new SaStorageForServlet(JbootControllerContext.get().getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验指定路由匹配符是否可以匹配成功指定路径
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SaTokenContext.super.isValid();
|
||||
|
||||
+9
-8
@@ -22,11 +22,20 @@ import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
/**
|
||||
* Sa-Token 上线文处理器 [Jfinal 版本实现]
|
||||
*/
|
||||
public class SaTokenContextForJfinal implements SaTokenContext {
|
||||
|
||||
public SaTokenContextForJfinal() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的Request对象
|
||||
*/
|
||||
@@ -51,14 +60,6 @@ public class SaTokenContextForJfinal implements SaTokenContext {
|
||||
return new SaStorageForServlet(SaControllerContext.get().getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验指定路由匹配符是否可以匹配成功指定路径
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SaTokenContext.super.isValid();
|
||||
|
||||
+1
-1
@@ -16,9 +16,9 @@
|
||||
package cn.dev33.satoken.jfinal;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
|
||||
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -31,11 +31,10 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- reactor-core (optional) -->
|
||||
<!-- reactor-core -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- jackson-databind (optional) -->
|
||||
@@ -52,23 +51,12 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -78,7 +66,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>5.3.7</version>
|
||||
<version>5.3.39</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
+53
-20
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.context;
|
||||
|
||||
import cn.dev33.satoken.fun.SaRetGenericFunction;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.context.ContextView;
|
||||
|
||||
/**
|
||||
* Reactor 上下文操作(异步),持有当前请求的 ServerWebExchange 全局引用
|
||||
@@ -28,37 +31,67 @@ import reactor.core.publisher.Mono;
|
||||
public class SaReactorHolder {
|
||||
|
||||
/**
|
||||
* key
|
||||
* ServerWebExchange key
|
||||
*/
|
||||
public static final Class<ServerWebExchange> CONTEXT_KEY = ServerWebExchange.class;
|
||||
public static final String EXCHANGE_KEY = "SA_REACTOR_EXCHANGE_KEY";
|
||||
|
||||
/**
|
||||
* chain_key
|
||||
* WebFilterChain key
|
||||
*/
|
||||
public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY";
|
||||
public static final String CHAIN_KEY = "SA_REACTOR__CHAIN_KEY";
|
||||
|
||||
/**
|
||||
* 获取上下文对象
|
||||
* @return see note
|
||||
* 在流式上下文写入 ServerWebExchange
|
||||
* @param ctx 必填
|
||||
* @param exchange 必填
|
||||
* @param chain 非必填
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<ServerWebExchange> getContext() {
|
||||
// 从全局 Mono<Context> 获取
|
||||
return Mono.subscriberContext().map(ctx -> ctx.get(CONTEXT_KEY));
|
||||
public static Context setContext(Context ctx, ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return ctx
|
||||
.put(EXCHANGE_KEY, exchange)
|
||||
.put(CHAIN_KEY, chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文对象, 并设置到同步上下文中
|
||||
* @return see note
|
||||
* 在流式上下文获取 ServerWebExchange
|
||||
* @param ctx /
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<ServerWebExchange> getContextAndSetSync() {
|
||||
// 从全局 Mono<Context> 获取
|
||||
return Mono.subscriberContext().map(ctx -> {
|
||||
// 设置到sync中
|
||||
SaReactorSyncHolder.setContext(ctx.get(CONTEXT_KEY));
|
||||
return ctx.get(CONTEXT_KEY);
|
||||
}).doFinally(r->{
|
||||
// 从sync中清除
|
||||
public static ServerWebExchange getExchange(ContextView ctx) {
|
||||
return ctx.get(EXCHANGE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在流式上下文获取 WebFilterChain
|
||||
* @param ctx /
|
||||
* @return /
|
||||
*/
|
||||
public static WebFilterChain getChain(ContextView ctx) {
|
||||
return ctx.get(CHAIN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mono < ServerWebExchange >
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<ServerWebExchange> getMonoExchange() {
|
||||
return Mono.deferContextual(ctx -> Mono.just(getExchange(ctx)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static <R> Mono<R> sync(SaRetGenericFunction<R> fun) {
|
||||
return Mono.deferContextual(ctx -> {
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(ctx.get(EXCHANGE_KEY));
|
||||
return Mono.just(fun.run());
|
||||
} finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+19
-20
@@ -15,17 +15,16 @@
|
||||
*/
|
||||
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.fun.SaRetGenericFunction;
|
||||
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaStorageForReactor;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Reactor上下文操作(同步),持有当前请求的 ServerWebExchange 全局引用
|
||||
@@ -36,8 +35,8 @@ import cn.dev33.satoken.reactor.model.SaStorageForReactor;
|
||||
public class SaReactorSyncHolder {
|
||||
|
||||
/**
|
||||
* 写入上下文对象
|
||||
* @param exchange see note
|
||||
* 在同步上下文写入 ServerWebExchange
|
||||
* @param exchange /
|
||||
*/
|
||||
public static void setContext(ServerWebExchange exchange) {
|
||||
SaRequest request = new SaRequestForReactor(exchange.getRequest());
|
||||
@@ -47,30 +46,30 @@ public class SaReactorSyncHolder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文对象
|
||||
* @return see note
|
||||
*/
|
||||
public static ServerWebExchange getContext() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (ServerWebExchange)box.getStorage().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文对象
|
||||
* 在同步上下文清除 ServerWebExchange
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaTokenContextForThreadLocalStorage.clearBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入上下文对象, 并在执行函数后将其清除
|
||||
* @param exchange see note
|
||||
* @param fun see note
|
||||
* 在同步上下文获取 ServerWebExchange
|
||||
* @return /
|
||||
*/
|
||||
public static void setContext(ServerWebExchange exchange, SaFunction fun) {
|
||||
public static ServerWebExchange getExchange() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (ServerWebExchange)box.getStorage().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文
|
||||
* @param exchange /
|
||||
* @param fun /
|
||||
*/
|
||||
public static <R>R setContext(ServerWebExchange exchange, SaRetGenericFunction<R> fun) {
|
||||
try {
|
||||
setContext(exchange);
|
||||
fun.run();
|
||||
return fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
|
||||
+12
-5
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
|
||||
import cn.dev33.satoken.reactor.util.SaReactorOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -43,21 +46,25 @@ public class SaFirewallCheckFilterForReactor implements WebFilter {
|
||||
SaResponseForReactor saResponse = new SaResponseForReactor(exchange.getResponse());
|
||||
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
SaFirewallStrategy.instance.check.execute(saRequest, saResponse, exchange);
|
||||
}
|
||||
catch (StopMatchException e) {
|
||||
// 如果是 StopMatchException 异常,代表通过了防火墙验证,进入 Controller
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
}
|
||||
catch (FirewallCheckException e) {
|
||||
// FirewallCheckException 异常则交由异常处理策略处理
|
||||
catch (FirewallCheckException e) {
|
||||
if(SaFirewallStrategy.instance.checkFailHandle == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
} else {
|
||||
SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
// 更多异常则不处理,交由 Web 框架处理
|
||||
|
||||
// 向下执行
|
||||
|
||||
+13
-44
@@ -21,9 +21,8 @@ import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
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.reactor.util.SaReactorOperateUtil;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -37,7 +36,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reactor 全局鉴权过滤器
|
||||
* 全局鉴权过滤器 (基于 Reactor)
|
||||
* <p>
|
||||
* 默认优先级为 -100,尽量保证在其它过滤器之前执行
|
||||
* </p>
|
||||
@@ -103,7 +102,7 @@ public class SaReactorFilter implements SaFilter, WebFilter {
|
||||
* 异常处理函数:每次[认证函数]发生异常时执行此函数
|
||||
*/
|
||||
public SaFilterErrorStrategy error = e -> {
|
||||
throw new SaTokenException(e).setCode(SaReactorSpringBootErrorCode.CODE_20205);
|
||||
throw new SaTokenException(e);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -136,54 +135,24 @@ public class SaReactorFilter implements SaFilter, WebFilter {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
|
||||
// 写入WebFilterChain对象
|
||||
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);
|
||||
|
||||
// ---------- 全局认证处理
|
||||
try {
|
||||
// 写入全局上下文 (同步)
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
|
||||
// 执行全局过滤器
|
||||
beforeAuth.run(null);
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
|
||||
auth.run(null);
|
||||
});
|
||||
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
|
||||
|
||||
// 2. 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> auth.run(null));
|
||||
}
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
|
||||
|
||||
} finally {
|
||||
// 清除上下文
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, String.valueOf(error.run(e)));
|
||||
}
|
||||
finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
|
||||
// ---------- 执行
|
||||
|
||||
// 写入全局上下文 (同步)
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
|
||||
// 执行
|
||||
return chain.filter(exchange).subscriberContext(ctx -> {
|
||||
// 写入全局上下文 (异步)
|
||||
ctx = ctx.put(SaReactorHolder.CONTEXT_KEY, exchange);
|
||||
return ctx;
|
||||
}).doFinally(r -> {
|
||||
// 清除上下文
|
||||
SaReactorSyncHolder.clearContext();
|
||||
});
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.reactor.filter;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorHolder;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
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 reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文初始化过滤器 (基于 Reactor)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Order(SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER)
|
||||
public class SaTokenContextFilterForReactor implements WebFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain.filter(exchange)
|
||||
.contextWrite(ctx -> SaReactorHolder.setContext(ctx, exchange, chain))
|
||||
.doFinally(r -> {
|
||||
// 在流式上下文中保存的数据会随着流式操作的结束而销毁,所以此处无需手动清除数据
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 这种写法有问题:
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
return chain.filter(exchange);
|
||||
} finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
}
|
||||
这种写法会先执行 finally,然后进入具体的 Controller
|
||||
*/
|
||||
+1
-1
@@ -184,7 +184,7 @@ public class SaRequestForReactor implements SaRequest {
|
||||
*/
|
||||
@Override
|
||||
public Object forward(String path) {
|
||||
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
|
||||
ServerWebExchange exchange = SaReactorSyncHolder.getExchange();
|
||||
WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY);
|
||||
|
||||
ServerHttpRequest newRequest = request.mutate().path(path).build();
|
||||
|
||||
+2
-9
@@ -16,9 +16,10 @@
|
||||
package cn.dev33.satoken.reactor.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
|
||||
/**
|
||||
* <h2> 此为低版本(<1.42.0) 的上下文处理方案,仅做留档,如无必要请勿使用 </h2>
|
||||
*
|
||||
* Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ] ,基于 SaTokenContextForThreadLocal 定制
|
||||
*
|
||||
* @author click33
|
||||
@@ -26,12 +27,4 @@ import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
*/
|
||||
public class SaTokenContextForSpringReactor extends SaTokenContextForThreadLocal {
|
||||
|
||||
/**
|
||||
* 重写路由匹配方法
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-6
@@ -16,10 +16,11 @@
|
||||
package cn.dev33.satoken.reactor.spring;
|
||||
|
||||
import cn.dev33.satoken.reactor.filter.SaFirewallCheckFilterForReactor;
|
||||
import cn.dev33.satoken.reactor.filter.SaTokenContextFilterForReactor;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 所需要的 Bean
|
||||
*
|
||||
@@ -28,18 +29,25 @@ import cn.dev33.satoken.context.SaTokenContext;
|
||||
*/
|
||||
public class SaTokenContextRegister {
|
||||
|
||||
public SaTokenContextRegister() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文处理器组件 (Spring Reactor 版)
|
||||
* 上下文过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
@Bean
|
||||
public SaTokenContext getSaTokenContextForSpringReactor() {
|
||||
return new SaTokenContextForSpringReactor();
|
||||
public SaTokenContextFilterForReactor saTokenContextFilterForServlet() {
|
||||
return new SaTokenContextFilterForReactor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 path 校验过滤器
|
||||
* 防火墙过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
|
||||
+2
-1
@@ -5,7 +5,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import org.springframework.boot.SpringBootVersion;
|
||||
|
||||
/**
|
||||
* SpringBoot 版本与 Sa-Token 版本兼容检查器
|
||||
* SpringBoot 版本与 Sa-Token 版本兼容检查器,当开发者错误的在 SpringBoot3.x 项目中引入当前集成包时,将在控制台做出提醒并阻断项目启动
|
||||
*
|
||||
* @author Uncarbon
|
||||
* @since 1.38.0
|
||||
@@ -22,4 +22,5 @@ public class SpringBootVersionCompatibilityChecker {
|
||||
System.err.println(str);
|
||||
throw new SaTokenException(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -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.reactor.util;
|
||||
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Reactor 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaReactorOperateUtil {
|
||||
|
||||
/**
|
||||
* 写入结果到输出流
|
||||
* @param exchange /
|
||||
* @param result /
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<Void> writeResult(ServerWebExchange exchange, String result) {
|
||||
// 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,15 +32,13 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<!--<version>5.3.7</version>-->
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- reactor-core (optional) -->
|
||||
<!-- reactor-core -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- jackson-databind (optional) -->
|
||||
@@ -57,23 +55,12 @@
|
||||
<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>
|
||||
@@ -85,25 +72,19 @@
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>${springboot3.version}</version>
|
||||
</dependency>
|
||||
<!-- spring-boot-starter-web -->
|
||||
<!-- spring-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>
|
||||
<version>6.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- jackson-databind (optional) -->
|
||||
<dependency>
|
||||
<!--<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.14.1</version>
|
||||
</dependency>
|
||||
</dependency>-->
|
||||
|
||||
<!-- config (optional) -->
|
||||
<dependency>
|
||||
|
||||
+54
-21
@@ -15,50 +15,83 @@
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.context;
|
||||
|
||||
import cn.dev33.satoken.fun.SaRetGenericFunction;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.context.ContextView;
|
||||
|
||||
/**
|
||||
* Reactor 上下文操作(异步),持有当前请求的 ServerWebExchange 全局引用
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.19.0
|
||||
*/
|
||||
public class SaReactorHolder {
|
||||
|
||||
/**
|
||||
* key
|
||||
* ServerWebExchange key
|
||||
*/
|
||||
public static final Class<ServerWebExchange> CONTEXT_KEY = ServerWebExchange.class;
|
||||
public static final String EXCHANGE_KEY = "SA_REACTOR_EXCHANGE_KEY";
|
||||
|
||||
/**
|
||||
* chain_key
|
||||
* WebFilterChain key
|
||||
*/
|
||||
public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY";
|
||||
public static final String CHAIN_KEY = "SA_REACTOR__CHAIN_KEY";
|
||||
|
||||
/**
|
||||
* 获取上下文对象
|
||||
* @return see note
|
||||
* 在流式上下文写入 ServerWebExchange
|
||||
* @param ctx 必填
|
||||
* @param exchange 必填
|
||||
* @param chain 非必填
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<ServerWebExchange> getContext() {
|
||||
// 从全局 Mono<Context> 获取
|
||||
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
|
||||
public static Context setContext(Context ctx, ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return ctx
|
||||
.put(EXCHANGE_KEY, exchange)
|
||||
.put(CHAIN_KEY, chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文对象, 并设置到同步上下文中
|
||||
* @return see note
|
||||
* 在流式上下文获取 ServerWebExchange
|
||||
* @param ctx /
|
||||
* @return /
|
||||
*/
|
||||
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中清除
|
||||
public static ServerWebExchange getExchange(ContextView ctx) {
|
||||
return ctx.get(EXCHANGE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在流式上下文获取 WebFilterChain
|
||||
* @param ctx /
|
||||
* @return /
|
||||
*/
|
||||
public static WebFilterChain getChain(ContextView ctx) {
|
||||
return ctx.get(CHAIN_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mono < ServerWebExchange >
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<ServerWebExchange> getMonoExchange() {
|
||||
return Mono.deferContextual(ctx -> Mono.just(getExchange(ctx)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static <R> Mono<R> sync(SaRetGenericFunction<R> fun) {
|
||||
return Mono.deferContextual(ctx -> {
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(ctx.get(EXCHANGE_KEY));
|
||||
return Mono.just(fun.run());
|
||||
} finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+20
-21
@@ -15,29 +15,28 @@
|
||||
*/
|
||||
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.fun.SaRetGenericFunction;
|
||||
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaStorageForReactor;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Reactor上下文操作(同步),持有当前请求的 ServerWebExchange 全局引用
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.19.0
|
||||
*/
|
||||
public class SaReactorSyncHolder {
|
||||
|
||||
/**
|
||||
* 写入上下文对象
|
||||
* @param exchange see note
|
||||
* 在同步上下文写入 ServerWebExchange
|
||||
* @param exchange /
|
||||
*/
|
||||
public static void setContext(ServerWebExchange exchange) {
|
||||
SaRequest request = new SaRequestForReactor(exchange.getRequest());
|
||||
@@ -47,30 +46,30 @@ public class SaReactorSyncHolder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文对象
|
||||
* @return see note
|
||||
*/
|
||||
public static ServerWebExchange getContext() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (ServerWebExchange)box.getStorage().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文对象
|
||||
* 在同步上下文清除 ServerWebExchange
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaTokenContextForThreadLocalStorage.clearBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入上下文对象, 并在执行函数后将其清除
|
||||
* @param exchange see note
|
||||
* @param fun see note
|
||||
* 在同步上下文获取 ServerWebExchange
|
||||
* @return /
|
||||
*/
|
||||
public static void setContext(ServerWebExchange exchange, SaFunction fun) {
|
||||
public static ServerWebExchange getExchange() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (ServerWebExchange)box.getStorage().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文
|
||||
* @param exchange /
|
||||
* @param fun /
|
||||
*/
|
||||
public static <R>R setContext(ServerWebExchange exchange, SaRetGenericFunction<R> fun) {
|
||||
try {
|
||||
setContext(exchange);
|
||||
fun.run();
|
||||
return fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
|
||||
+12
-5
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
|
||||
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
|
||||
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
|
||||
import cn.dev33.satoken.reactor.util.SaReactorOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -43,21 +46,25 @@ public class SaFirewallCheckFilterForReactor implements WebFilter {
|
||||
SaResponseForReactor saResponse = new SaResponseForReactor(exchange.getResponse());
|
||||
|
||||
try {
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
SaFirewallStrategy.instance.check.execute(saRequest, saResponse, exchange);
|
||||
}
|
||||
catch (StopMatchException e) {
|
||||
// 如果是 StopMatchException 异常,代表通过了防火墙验证,进入 Controller
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
}
|
||||
catch (FirewallCheckException e) {
|
||||
// FirewallCheckException 异常则交由异常处理策略处理
|
||||
catch (FirewallCheckException e) {
|
||||
if(SaFirewallStrategy.instance.checkFailHandle == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
} else {
|
||||
SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
SaReactorSyncHolder.clearContext();
|
||||
}
|
||||
// 更多异常则不处理,交由 Web 框架处理
|
||||
|
||||
// 向下执行
|
||||
|
||||
+12
-45
@@ -21,9 +21,8 @@ import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
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.reactor.util.SaReactorOperateUtil;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -96,7 +95,7 @@ public class SaReactorFilter implements SaFilter, WebFilter {
|
||||
* 异常处理函数:每次[认证函数]发生异常时执行此函数
|
||||
*/
|
||||
public SaFilterErrorStrategy error = e -> {
|
||||
throw new SaTokenException(e).setCode(SaReactorSpringBootErrorCode.CODE_20205);
|
||||
throw new SaTokenException(e);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -123,60 +122,28 @@ public class SaReactorFilter implements SaFilter, WebFilter {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------ filter
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
|
||||
// 写入WebFilterChain对象
|
||||
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);
|
||||
|
||||
// ---------- 全局认证处理
|
||||
try {
|
||||
// 写入全局上下文 (同步)
|
||||
SaReactorSyncHolder.setContext(exchange);
|
||||
|
||||
// 执行全局过滤器
|
||||
beforeAuth.run(null);
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
|
||||
auth.run(null);
|
||||
});
|
||||
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
|
||||
|
||||
// 2. 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> auth.run(null));
|
||||
}
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
|
||||
|
||||
} finally {
|
||||
// 清除上下文
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, e.getMessage());
|
||||
}
|
||||
catch (Throwable e) {
|
||||
return SaReactorOperateUtil.writeResult(exchange, String.valueOf(error.run(e)));
|
||||
}
|
||||
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();
|
||||
});
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.reactor.filter;
|
||||
|
||||
import cn.dev33.satoken.reactor.context.SaReactorHolder;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
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 reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文初始化过滤器 (基于 Reactor)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Order(SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER)
|
||||
public class SaTokenContextFilterForReactor implements WebFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain.filter(exchange)
|
||||
.contextWrite(ctx -> SaReactorHolder.setContext(ctx, exchange, chain))
|
||||
.doFinally(r -> {
|
||||
// 在流式上下文中保存的数据会随着流式操作的结束而销毁,所以此处无需手动清除数据
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -184,7 +184,7 @@ public class SaRequestForReactor implements SaRequest {
|
||||
*/
|
||||
@Override
|
||||
public Object forward(String path) {
|
||||
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
|
||||
ServerWebExchange exchange = SaReactorSyncHolder.getExchange();
|
||||
WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY);
|
||||
|
||||
ServerHttpRequest newRequest = request.mutate().path(path).build();
|
||||
|
||||
+3
-10
@@ -16,22 +16,15 @@
|
||||
package cn.dev33.satoken.reactor.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
|
||||
/**
|
||||
* <h2> 此为低版本(<1.42.0) 的上下文处理方案,仅做留档,如无必要请勿使用 </h2>
|
||||
*
|
||||
* Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ] ,基于 SaTokenContextForThreadLocal 定制
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.33.0
|
||||
*/
|
||||
public class SaTokenContextForSpringReactor extends SaTokenContextForThreadLocal {
|
||||
|
||||
/**
|
||||
* 重写路由匹配方法
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-5
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package cn.dev33.satoken.reactor.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
import cn.dev33.satoken.reactor.filter.SaFirewallCheckFilterForReactor;
|
||||
import cn.dev33.satoken.reactor.filter.SaTokenContextFilterForReactor;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
@@ -27,18 +29,25 @@ import org.springframework.context.annotation.Bean;
|
||||
*/
|
||||
public class SaTokenContextRegister {
|
||||
|
||||
public SaTokenContextRegister() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文处理器组件 (Spring Reactor 版)
|
||||
* 上下文过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
@Bean
|
||||
public SaTokenContext getSaTokenContextForSpringReactor() {
|
||||
return new SaTokenContextForSpringReactor();
|
||||
public SaTokenContextFilterForReactor saTokenContextFilterForServlet() {
|
||||
return new SaTokenContextFilterForReactor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 path 校验过滤器
|
||||
* 防火墙过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
|
||||
+46
@@ -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.reactor.util;
|
||||
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Reactor 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaReactorOperateUtil {
|
||||
|
||||
/**
|
||||
* 写入结果到输出流
|
||||
* @param exchange /
|
||||
* @param result /
|
||||
* @return /
|
||||
*/
|
||||
public static Mono<Void> writeResult(ServerWebExchange exchange, String result) {
|
||||
// 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(exchange.getResponse().getHeaders().getFirst(SaTokenConsts.CONTENT_TYPE_KEY) == null) {
|
||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -187,7 +187,7 @@ public class SaRequestForServlet implements SaRequest {
|
||||
@Override
|
||||
public Object forward(String path) {
|
||||
try {
|
||||
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContextOrSecond().getResponse().getSource();
|
||||
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContext().getResponse().getSource();
|
||||
request.getRequestDispatcher(path).forward(request, response);
|
||||
return null;
|
||||
} catch (ServletException | IOException e) {
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.servlet.util;
|
||||
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Servlet 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaServletOperateUtil {
|
||||
|
||||
/**
|
||||
* 写入结果到输出流
|
||||
* @param response /
|
||||
* @param result /
|
||||
*/
|
||||
public static void writeResult(ServletResponse response, String result) throws IOException {
|
||||
// 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(response.getContentType() == null) {
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
response.getWriter().print(result);
|
||||
response.getWriter().flush();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.servlet.util;
|
||||
|
||||
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.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文读写工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTokenContextUtil {
|
||||
|
||||
/**
|
||||
* 写入当前上下文
|
||||
* @param request /
|
||||
* @param response /
|
||||
*/
|
||||
public static void setContext(HttpServletRequest request, HttpServletResponse response) {
|
||||
SaRequest req = new SaRequestForServlet(request);
|
||||
SaResponse res = new SaResponseForServlet(response);
|
||||
SaStorage stg = new SaStorageForServlet(request);
|
||||
SaTokenContextForThreadLocalStorage.setBox(req, res, stg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前上下文
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaTokenContextForThreadLocalStorage.clearBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入上下文对象, 并在执行函数后将其清除
|
||||
* @param request /
|
||||
* @param response /
|
||||
* @param fun /
|
||||
*/
|
||||
public static void setContext(HttpServletRequest request, HttpServletResponse response, SaFunction fun) {
|
||||
try {
|
||||
setContext(request, response);
|
||||
fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Box
|
||||
* @return /
|
||||
*/
|
||||
public static Box getBox() {
|
||||
return SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Request
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (HttpServletRequest) box.getRequest().getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Response
|
||||
* @return /
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (HttpServletResponse) box.getResponse().getSource();
|
||||
}
|
||||
|
||||
}
|
||||
+21
-1
@@ -17,11 +17,14 @@ package cn.dev33.satoken.solon;
|
||||
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.solon.integration.SaFirewallCheckFilterForSolon;
|
||||
import cn.dev33.satoken.solon.integration.SaTokenContextFilterForSolon;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.util.PathAnalyzer;
|
||||
|
||||
/**
|
||||
* 注册Sa-Token所需要的Bean
|
||||
@@ -32,6 +35,13 @@ import org.noear.solon.core.handle.Filter;
|
||||
@Configuration
|
||||
public class SaBeanRegister {
|
||||
|
||||
public SaBeanRegister() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置Bean
|
||||
*
|
||||
@@ -47,7 +57,17 @@ public class SaBeanRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 防火墙校验过滤器
|
||||
* 上下文过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
@Bean(index = SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER)
|
||||
public Filter saTokenContextFilterForSolon() {
|
||||
return new SaTokenContextFilterForSolon();
|
||||
}
|
||||
|
||||
/**
|
||||
* 防火墙过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
|
||||
-3
@@ -17,7 +17,6 @@ package cn.dev33.satoken.solon;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.solon.json.SaJsonTemplateForSnack3;
|
||||
import cn.dev33.satoken.solon.model.SaContextForSolon;
|
||||
import cn.dev33.satoken.solon.oauth2.SaOAuth2BeanInject;
|
||||
import cn.dev33.satoken.solon.oauth2.SaOAuth2BeanRegister;
|
||||
import cn.dev33.satoken.solon.sso.SaSsoBeanInject;
|
||||
@@ -33,8 +32,6 @@ public class SaSolonPlugin implements Plugin {
|
||||
|
||||
@Override
|
||||
public void start(AppContext context) {
|
||||
// 注入上下文Bean
|
||||
SaManager.setSaTokenContext(new SaContextForSolon());
|
||||
|
||||
// 注入JSON解析器Bean
|
||||
SaManager.setSaJsonTemplate(new SaJsonTemplateForSnack3());
|
||||
|
||||
+7
-5
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package cn.dev33.satoken.solon.integration;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.solon.model.SaRequestForSolon;
|
||||
import cn.dev33.satoken.solon.model.SaResponseForSolon;
|
||||
import cn.dev33.satoken.solon.util.SaSolonOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
@@ -41,17 +43,17 @@ public class SaFirewallCheckFilterForSolon implements Filter {
|
||||
try {
|
||||
SaFirewallStrategy.instance.check.execute(saRequest, saResponse, null);
|
||||
}
|
||||
catch (StopMatchException e) {
|
||||
// 如果是 StopMatchException 异常,代表通过了防火墙验证,进入 Controller
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaSolonOperateUtil.writeResult(ctx, e.getMessage());
|
||||
return;
|
||||
}
|
||||
catch (FirewallCheckException e) {
|
||||
// FirewallCheckException 异常则交由异常处理策略处理
|
||||
if(SaFirewallStrategy.instance.checkFailHandle == null) {
|
||||
ctx.render(e.getMessage());
|
||||
SaSolonOperateUtil.writeResult(ctx, e.getMessage());
|
||||
} else {
|
||||
SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null);
|
||||
}
|
||||
ctx.setHandled(true);
|
||||
return;
|
||||
}
|
||||
// 更多异常则不处理,交由 Web 框架处理
|
||||
|
||||
+41
@@ -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.solon.integration;
|
||||
|
||||
import cn.dev33.satoken.solon.util.SaTokenContextUtil;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
/**
|
||||
* 上下文初始化过滤器 (基于 Solon)
|
||||
*
|
||||
* @author noear
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTokenContextFilterForSolon implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
try {
|
||||
SaTokenContextUtil.setContext(ctx);
|
||||
chain.doFilter(ctx);
|
||||
} finally {
|
||||
SaTokenContextUtil.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
-15
@@ -18,10 +18,11 @@ package cn.dev33.satoken.solon.integration;
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
|
||||
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
|
||||
import cn.dev33.satoken.filter.SaFilter;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.solon.util.SaSolonOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.core.handle.*;
|
||||
@@ -180,22 +181,14 @@ public class SaTokenFilter implements SaFilter, Filter { //之所以改名,为
|
||||
auth.run(finalMainHandler);
|
||||
}
|
||||
});
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
} catch (SaTokenException e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
Object result;
|
||||
if (e instanceof BackResultException) {
|
||||
result = e.getMessage();
|
||||
} else {
|
||||
result = error.run(e);
|
||||
}
|
||||
|
||||
// 2. 写入输出流
|
||||
if (result != null) {
|
||||
ctx.render(result);
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaSolonOperateUtil.writeResult(ctx, e.getMessage());
|
||||
return;
|
||||
}
|
||||
ctx.setHandled(true);
|
||||
catch (SaTokenException e) {
|
||||
SaSolonOperateUtil.writeResult(ctx, error.run(e));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+7
-14
@@ -22,6 +22,7 @@ import cn.dev33.satoken.filter.SaFilter;
|
||||
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
|
||||
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.solon.util.SaSolonOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
|
||||
import org.noear.solon.core.handle.*;
|
||||
import org.noear.solon.core.route.RouterInterceptor;
|
||||
@@ -211,22 +212,14 @@ public class SaTokenInterceptor implements SaFilter, RouterInterceptor {
|
||||
}
|
||||
});
|
||||
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
} catch (SaTokenException e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
Object result;
|
||||
if (e instanceof BackResultException) {
|
||||
result = e.getMessage();
|
||||
} else {
|
||||
result = error.run(e);
|
||||
}
|
||||
|
||||
// 2. 写入输出流
|
||||
if (result != null) {
|
||||
ctx.render(result);
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaSolonOperateUtil.writeResult(ctx, e.getMessage());
|
||||
return;
|
||||
}
|
||||
ctx.setHandled(true);
|
||||
catch (SaTokenException e) {
|
||||
SaSolonOperateUtil.writeResult(ctx, error.run(e));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+3
-9
@@ -20,13 +20,15 @@ import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.util.PathAnalyzer;
|
||||
|
||||
/**
|
||||
* <h2> 此为低版本(<1.42.0) 的上下文处理方案,基于 Solon 内部封装 Context.current() 读写上下文,仅做留档,如无必要请勿使用 </h2>
|
||||
*
|
||||
* @author noear
|
||||
* @since 1.4
|
||||
*/
|
||||
public class SaContextForSolon implements SaTokenContext {
|
||||
|
||||
/**
|
||||
* 获取当前请求的Request对象
|
||||
*/
|
||||
@@ -51,14 +53,6 @@ public class SaContextForSolon implements SaTokenContext {
|
||||
return new SaStorageForSolon();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验指定路由匹配符是否可以匹配成功指定路径
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return PathAnalyzer.get(pattern).matches(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此上下文是否有效
|
||||
* @return /
|
||||
|
||||
+5
-1
@@ -32,7 +32,11 @@ public class SaRequestForSolon implements SaRequest {
|
||||
protected Context ctx;
|
||||
|
||||
public SaRequestForSolon() {
|
||||
ctx = Context.current();
|
||||
this(Context.current());
|
||||
}
|
||||
|
||||
public SaRequestForSolon(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-1
@@ -27,7 +27,11 @@ public class SaResponseForSolon implements SaResponse {
|
||||
protected Context ctx;
|
||||
|
||||
public SaResponseForSolon() {
|
||||
ctx = Context.current();
|
||||
this(Context.current());
|
||||
}
|
||||
|
||||
public SaResponseForSolon(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-1
@@ -27,7 +27,11 @@ public class SaStorageForSolon implements SaStorage {
|
||||
protected Context ctx;
|
||||
|
||||
public SaStorageForSolon() {
|
||||
ctx = Context.current();
|
||||
this(Context.current());
|
||||
}
|
||||
|
||||
public SaStorageForSolon(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+17
-17
@@ -13,29 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.error;
|
||||
package cn.dev33.satoken.solon.util;
|
||||
|
||||
import org.noear.solon.core.handle.Context;
|
||||
|
||||
/**
|
||||
* 定义 sa-token-spring-boot-starter 所有异常细分状态码
|
||||
* Solon 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public interface SaSpringBootErrorCode {
|
||||
public class SaSolonOperateUtil {
|
||||
|
||||
/** 企图在非 Web 上下文获取 Request、Response 等对象 */
|
||||
int CODE_20101 = 20101;
|
||||
/**
|
||||
* 写入结果到输出流
|
||||
* @param ctx /
|
||||
* @param result /
|
||||
*/
|
||||
public static void writeResult(Context ctx, Object result) throws Throwable {
|
||||
if (result != null) {
|
||||
ctx.render(result);
|
||||
}
|
||||
ctx.setHandled(true);
|
||||
}
|
||||
|
||||
/** 对象转 JSON 字符串失败 */
|
||||
int CODE_20103 = 20103;
|
||||
|
||||
/** JSON 字符串转 Map 失败 */
|
||||
int CODE_20104 = 20104;
|
||||
|
||||
/** JSON 字符串转 Object 失败 */
|
||||
int CODE_20106 = 20106;
|
||||
|
||||
/** 默认的 Filter 异常处理函数 */
|
||||
int CODE_20105 = 20105;
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.solon.util;
|
||||
|
||||
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.solon.model.SaRequestForSolon;
|
||||
import cn.dev33.satoken.solon.model.SaResponseForSolon;
|
||||
import cn.dev33.satoken.solon.model.SaStorageForSolon;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文读写工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTokenContextUtil {
|
||||
|
||||
/**
|
||||
* 写入当前上下文
|
||||
*/
|
||||
public static void setContext(Context ctx) {
|
||||
SaRequest req = new SaRequestForSolon(ctx);
|
||||
SaResponse res = new SaResponseForSolon(ctx);
|
||||
SaStorage stg = new SaStorageForSolon(ctx);
|
||||
SaTokenContextForThreadLocalStorage.setBox(req, res, stg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前上下文
|
||||
*/
|
||||
public static void clearContext() {
|
||||
SaTokenContextForThreadLocalStorage.clearBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入上下文对象, 并在执行函数后将其清除
|
||||
* @param ctx /
|
||||
* @param fun /
|
||||
*/
|
||||
public static void setContext(Context ctx, SaFunction fun) {
|
||||
try {
|
||||
setContext(ctx);
|
||||
fun.run();
|
||||
} finally {
|
||||
clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Box
|
||||
* @return /
|
||||
*/
|
||||
public static Box getBox() {
|
||||
return SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Context
|
||||
* @return /
|
||||
*/
|
||||
public static Context getContext() {
|
||||
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
|
||||
return (Context) box.getStorage().getSource();
|
||||
}
|
||||
|
||||
}
|
||||
+7
-16
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package cn.dev33.satoken.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.util.SaServletOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -48,15 +50,14 @@ public class SaFirewallCheckFilterForServlet implements Filter {
|
||||
try {
|
||||
SaFirewallStrategy.instance.check.execute(saRequest, saResponse, null);
|
||||
}
|
||||
catch (StopMatchException e) {
|
||||
// 如果是 StopMatchException 异常,代表通过了防火墙验证,进入 Controller
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
return;
|
||||
}
|
||||
catch (FirewallCheckException e) {
|
||||
// FirewallCheckException 异常则交由异常处理策略处理
|
||||
if(SaFirewallStrategy.instance.checkFailHandle == null) {
|
||||
response.setContentType("text/plain; charset=utf-8");
|
||||
response.getWriter().print(e.getMessage());
|
||||
response.getWriter().flush();
|
||||
SaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
} else {
|
||||
SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null);
|
||||
}
|
||||
@@ -68,14 +69,4 @@ public class SaFirewallCheckFilterForServlet implements Filter {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+10
-27
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package cn.dev33.satoken.filter;
|
||||
|
||||
import cn.dev33.satoken.error.SaSpringBootErrorCode;
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.servlet.util.SaServletOperateUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Servlet 全局鉴权过滤器
|
||||
* 全局鉴权过滤器 (基于 Servlet)
|
||||
* <p>
|
||||
* 默认优先级为 -100,尽量保证在其它过滤器之前执行
|
||||
* </p>
|
||||
@@ -89,7 +89,7 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
* 异常处理函数:每次[认证函数]发生异常时执行此函数
|
||||
*/
|
||||
public SaFilterErrorStrategy error = e -> {
|
||||
throw new SaTokenException(e).setCode(SaSpringBootErrorCode.CODE_20105);
|
||||
throw new SaTokenException(e);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,21 +128,14 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
|
||||
auth.run(null);
|
||||
});
|
||||
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
|
||||
|
||||
// 2. 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(response.getContentType() == null) {
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
response.getWriter().print(result);
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
return;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
SaServletOperateUtil.writeResult(response, String.valueOf(error.run(e)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,14 +143,4 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -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.filter;
|
||||
|
||||
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文初始化过滤器 (基于 Servlet)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Order(SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER)
|
||||
public class SaTokenContextFilterForServlet implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
try {
|
||||
SaTokenContextUtil.setContext((HttpServletRequest) request, (HttpServletResponse) response);
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
SaTokenContextUtil.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
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.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPatternsRequestConditionHolder;
|
||||
|
||||
/**
|
||||
* Sa-Token 上下文处理器 [ SpringMVC版本实现 ]。在 SpringMVC、SpringBoot 中使用 Sa-Token 时,必须注入此实现类,否则会出现上下文无效异常
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.19.0
|
||||
*/
|
||||
public class SaTokenContextForSpring implements SaTokenContext {
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Request 包装对象
|
||||
*/
|
||||
@Override
|
||||
public SaRequest getRequest() {
|
||||
return new SaRequestForServlet(SpringMVCUtil.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Response 包装对象
|
||||
*/
|
||||
@Override
|
||||
public SaResponse getResponse() {
|
||||
return new SaResponseForServlet(SpringMVCUtil.getResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求的 Storage 包装对象
|
||||
*/
|
||||
@Override
|
||||
public SaStorage getStorage() {
|
||||
return new SaStorageForServlet(SpringMVCUtil.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定路由匹配符是否可以匹配成功指定路径
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return SaPatternsRequestConditionHolder.match(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:在本次请求中,此上下文是否可用。
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return SpringMVCUtil.isWeb();
|
||||
}
|
||||
|
||||
}
|
||||
+14
-5
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package cn.dev33.satoken.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
import cn.dev33.satoken.filter.SaFirewallCheckFilterForServlet;
|
||||
import cn.dev33.satoken.filter.SaTokenContextFilterForServlet;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPatternsRequestConditionHolder;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
@@ -27,18 +29,25 @@ import org.springframework.context.annotation.Bean;
|
||||
*/
|
||||
public class SaTokenContextRegister {
|
||||
|
||||
public SaTokenContextRegister() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return SaPatternsRequestConditionHolder.match(pattern, path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文处理器组件 (Spring版)
|
||||
* 上下文过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
@Bean
|
||||
public SaTokenContext getSaTokenContextForSpring() {
|
||||
return new SaTokenContextForSpring();
|
||||
public SaTokenContextFilterForServlet saTokenContextFilterForServlet() {
|
||||
return new SaTokenContextFilterForServlet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 path 校验过滤器
|
||||
* 防火墙过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import org.springframework.boot.SpringBootVersion;
|
||||
|
||||
/**
|
||||
* SpringBoot 版本与 Sa-Token 版本兼容检查器
|
||||
* SpringBoot 版本与 Sa-Token 版本兼容检查器,当开发者错误的在 SpringBoot3.x 项目中引入当前集成包时,将在控制台做出提醒并阻断项目启动
|
||||
*
|
||||
* @author Uncarbon
|
||||
* @since 1.38.0
|
||||
|
||||
+5
-7
@@ -15,14 +15,12 @@
|
||||
*/
|
||||
package cn.dev33.satoken.spring;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import cn.dev33.satoken.exception.NotWebContextException;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import cn.dev33.satoken.error.SaSpringBootErrorCode;
|
||||
import cn.dev33.satoken.exception.NotWebContextException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* SpringMVC 相关操作工具类,快速获取当前会话的 HttpServletRequest、HttpServletResponse 对象
|
||||
@@ -42,7 +40,7 @@ public class SpringMVCUtil {
|
||||
public static HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if(servletRequestAttributes == null) {
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest").setCode(SaSpringBootErrorCode.CODE_20101);
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest");
|
||||
}
|
||||
return servletRequestAttributes.getRequest();
|
||||
}
|
||||
@@ -54,7 +52,7 @@ public class SpringMVCUtil {
|
||||
public static HttpServletResponse getResponse() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if(servletRequestAttributes == null) {
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletResponse").setCode(SaSpringBootErrorCode.CODE_20101);
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletResponse");
|
||||
}
|
||||
return servletRequestAttributes.getResponse();
|
||||
}
|
||||
|
||||
+7
-15
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package cn.dev33.satoken.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.FirewallCheckException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.util.SaJakartaServletOperateUtil;
|
||||
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import jakarta.servlet.*;
|
||||
@@ -48,15 +50,14 @@ public class SaFirewallCheckFilterForJakartaServlet implements Filter {
|
||||
try {
|
||||
SaFirewallStrategy.instance.check.execute(saRequest, saResponse, null);
|
||||
}
|
||||
catch (StopMatchException e) {
|
||||
// 如果是 StopMatchException 异常,代表通过了防火墙验证,进入 Controller
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaJakartaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
return;
|
||||
}
|
||||
catch (FirewallCheckException e) {
|
||||
// FirewallCheckException 异常则交由异常处理策略处理
|
||||
if(SaFirewallStrategy.instance.checkFailHandle == null) {
|
||||
response.setContentType("text/plain; charset=utf-8");
|
||||
response.getWriter().print(e.getMessage());
|
||||
response.getWriter().flush();
|
||||
SaJakartaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
} else {
|
||||
SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null);
|
||||
}
|
||||
@@ -68,14 +69,5 @@ public class SaFirewallCheckFilterForJakartaServlet implements Filter {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+10
-27
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package cn.dev33.satoken.filter;
|
||||
|
||||
import cn.dev33.satoken.error.SaSpringBootErrorCode;
|
||||
import cn.dev33.satoken.exception.BackResultException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.exception.StopMatchException;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.servlet.util.SaJakartaServletOperateUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import jakarta.servlet.*;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -30,7 +30,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Jakarta-Servlet 全局鉴权过滤器
|
||||
* 全局鉴权过滤器 (基于 Jakarta-Servlet)
|
||||
* <p>
|
||||
* 默认优先级为 -100,尽量保证在其它过滤器之前执行
|
||||
* </p>
|
||||
@@ -89,7 +89,7 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
* 异常处理函数:每次[认证函数]发生异常时执行此函数
|
||||
*/
|
||||
public SaFilterErrorStrategy error = e -> {
|
||||
throw new SaTokenException(e).setCode(SaSpringBootErrorCode.CODE_20105);
|
||||
throw new SaTokenException(e);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -128,21 +128,14 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
|
||||
auth.run(null);
|
||||
});
|
||||
|
||||
} catch (StopMatchException e) {
|
||||
// StopMatchException 异常代表:停止匹配,进入Controller
|
||||
|
||||
} catch (Throwable e) {
|
||||
// 1. 获取异常处理策略结果
|
||||
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
|
||||
|
||||
// 2. 写入输出流
|
||||
// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json
|
||||
// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
if(response.getContentType() == null) {
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||
}
|
||||
response.getWriter().print(result);
|
||||
catch (StopMatchException ignored) {}
|
||||
catch (BackResultException e) {
|
||||
SaJakartaServletOperateUtil.writeResult(response, e.getMessage());
|
||||
return;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
SaJakartaServletOperateUtil.writeResult(response, String.valueOf(error.run(e)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,14 +143,4 @@ public class SaServletFilter implements SaFilter, Filter {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -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.filter;
|
||||
|
||||
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SaTokenContext 上下文初始化过滤器 (基于 Jakarta-Servlet)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Order(SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER)
|
||||
public class SaTokenContextFilterForJakartaServlet implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
try {
|
||||
SaTokenContextUtil.setContext((HttpServletRequest) request, (HttpServletResponse) response);
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
SaTokenContextUtil.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-9
@@ -22,9 +22,10 @@ import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
|
||||
import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
|
||||
/**
|
||||
* <h2> 此为低版本(<1.42.0) 的上下文处理方案,基于 Spring 内部工具类 RequestContextHolder 读写上下文,仅做留档,如无必要请勿使用 </h2>
|
||||
*
|
||||
* Sa-Token 上下文处理器 [ SpringBoot3 Jakarta Servlet 版 ],在 SpringBoot3 中使用 Sa-Token 时,必须注入此实现类,否则会出现上下文无效异常
|
||||
*
|
||||
* @author click33
|
||||
@@ -56,14 +57,6 @@ public class SaTokenContextForSpringInJakartaServlet implements SaTokenContext {
|
||||
return new SaStorageForServlet(SpringMVCUtil.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定路由匹配符是否可以匹配成功指定路径
|
||||
*/
|
||||
@Override
|
||||
public boolean matchPath(String pattern, String path) {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:在本次请求中,此上下文是否可用。
|
||||
*/
|
||||
|
||||
+14
-5
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package cn.dev33.satoken.spring;
|
||||
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
import cn.dev33.satoken.filter.SaFirewallCheckFilterForJakartaServlet;
|
||||
import cn.dev33.satoken.filter.SaTokenContextFilterForJakartaServlet;
|
||||
import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
@@ -27,18 +29,25 @@ import org.springframework.context.annotation.Bean;
|
||||
*/
|
||||
public class SaTokenContextRegister {
|
||||
|
||||
public SaTokenContextRegister() {
|
||||
// 重写路由匹配算法
|
||||
SaStrategy.instance.routeMatcher = (pattern, path) -> {
|
||||
return SaPathPatternParserUtil.match(pattern, path);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文处理器组件 (SpringBoot3 Jakarta Servlet 版)
|
||||
* 上下文过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
@Bean
|
||||
public SaTokenContext getSaTokenContextForSpringInJakartaServlet() {
|
||||
return new SaTokenContextForSpringInJakartaServlet();
|
||||
public SaTokenContextFilterForJakartaServlet saTokenContextFilterForServlet() {
|
||||
return new SaTokenContextFilterForJakartaServlet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 path 校验过滤器
|
||||
* 防火墙过滤器
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
|
||||
+4
-6
@@ -15,13 +15,11 @@
|
||||
*/
|
||||
package cn.dev33.satoken.spring;
|
||||
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import cn.dev33.satoken.error.SaSpringBootErrorCode;
|
||||
import cn.dev33.satoken.exception.NotWebContextException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* SpringMVC 相关操作工具类,快速获取当前会话的 HttpServletRequest、HttpServletResponse 对象
|
||||
@@ -41,7 +39,7 @@ public class SpringMVCUtil {
|
||||
public static HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if(servletRequestAttributes == null) {
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest").setCode(SaSpringBootErrorCode.CODE_20101);
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest");
|
||||
}
|
||||
return servletRequestAttributes.getRequest();
|
||||
}
|
||||
@@ -53,7 +51,7 @@ public class SpringMVCUtil {
|
||||
public static HttpServletResponse getResponse() {
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if(servletRequestAttributes == null) {
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest").setCode(SaSpringBootErrorCode.CODE_20101);
|
||||
throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest");
|
||||
}
|
||||
return servletRequestAttributes.getResponse();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user