新增Reactor响应式编程支持,WebFlux集成!

This commit is contained in:
shengzhang
2021-04-11 22:05:36 +08:00
parent 15eefbed33
commit 8c6cd9a668
57 changed files with 1786 additions and 154 deletions

View File

@@ -19,7 +19,9 @@
<!-- 所有模块 -->
<modules>
<module>sa-token-core</module>
<module>sa-token-servlet</module>
<module>sa-token-spring-boot-starter</module>
<module>sa-token-reactor-spring-boot-starter</module>
<module>sa-token-dao-redis</module>
<module>sa-token-dao-redis-jackson</module>
<module>sa-token-spring-aop</module>

View File

@@ -11,6 +11,10 @@ import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategyDefaultImpl;
import cn.dev33.satoken.filter.SaFilterStrategy;
import cn.dev33.satoken.filter.SaFilterStrategyDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
@@ -124,6 +128,42 @@ public class SaTokenManager {
return saTokenContext;
}
/**
* 全局过滤器-认证策略 Bean
*/
private static SaFilterStrategy strategy;
public static void setSaFilterStrategy(SaFilterStrategy strategy) {
SaTokenManager.strategy = strategy;
}
public static SaFilterStrategy getSaFilterStrategy() {
if (strategy == null) {
synchronized (SaTokenManager.class) {
if (strategy == null) {
setSaFilterStrategy(new SaFilterStrategyDefaultImpl());
}
}
}
return strategy;
}
/**
* 全局过滤器-异常处理策略 Bean
*/
private static SaFilterErrorStrategy errorStrategy;
public static void setSaFilterErrorStrategy(SaFilterErrorStrategy errorStrategy) {
SaTokenManager.errorStrategy = errorStrategy;
}
public static SaFilterErrorStrategy getSaFilterErrorStrategy() {
if (errorStrategy == null) {
synchronized (SaTokenManager.class) {
if (errorStrategy == null) {
setSaFilterErrorStrategy(new SaFilterErrorStrategyDefaultImpl());
}
}
}
return errorStrategy;
}
/**
* StpLogic集合, 记录框架所有成功初始化的StpLogic
*/

View File

@@ -1,29 +1,37 @@
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;
/**
* 与底层容器交互接口
* Sa-Token 上下文处理器
* @author kong
*
*/
public interface SaTokenContext {
/**
* 获取当前请求的 Request 对象
* 获取当前请求的 [Request] 对象
*
* @return see note
*/
public SaRequest getRequest();
/**
* 获取当前请求的 Response 对象
* 获取当前请求的 [Response] 对象
*
* @return see note
*/
public SaResponse getResponse();
/**
* 获取当前请求的 [存储器] 对象
*
* @return see note
*/
public SaStorage getStorage();
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
*

View File

@@ -1,30 +1,44 @@
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.exception.SaTokenException;
/**
* Sa-Token 与底层容器交互接口 [默认实现类]
* Sa-Token 上下文处理器 [默认实现类]
* @author kong
*
*/
public class SaTokenContextDefaultImpl implements SaTokenContext {
/**
* 获取当前请求的Request对象
* 默认的错误提示语
*/
public static final String ERROR_MESSAGE = "未初始化任何有效上下文处理器";
/**
* 获取当前请求的 [Request] 对象
*/
@Override
public SaRequest getRequest() {
throw new SaTokenException("未初始化任何有效容器");
throw new SaTokenException(ERROR_MESSAGE);
}
/**
* 获取当前请求的Response对象
* 获取当前请求的 [Response] 对象
*/
@Override
public SaResponse getResponse() {
throw new SaTokenException("未初始化任何有效容器");
throw new SaTokenException(ERROR_MESSAGE);
}
/**
* 获取当前请求的 [存储器] 对象
*/
@Override
public SaStorage getStorage() {
throw new SaTokenException(ERROR_MESSAGE);
}
/**
@@ -32,7 +46,9 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
*/
@Override
public boolean matchPath(String pattern, String path) {
throw new SaTokenException("未初始化任何有效容器");
throw new SaTokenException(ERROR_MESSAGE);
}
}

View File

@@ -0,0 +1,34 @@
package cn.dev33.satoken.context;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
/**
* 上下文环境 [ThreadLocal版本]
* @author kong
*
*/
public class SaTokenContextForThreadLocal implements SaTokenContext {
@Override
public SaRequest getRequest() {
return SaTokenContextForThreadLocalStorage.getRequest();
}
@Override
public SaResponse getResponse() {
return SaTokenContextForThreadLocalStorage.getResponse();
}
@Override
public SaStorage getStorage() {
return SaTokenContextForThreadLocalStorage.getStorage();
}
@Override
public boolean matchPath(String pattern, String path) {
return false;
}
}

View File

@@ -0,0 +1,139 @@
package cn.dev33.satoken.context;
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.exception.SaTokenException;
/**
* 基于ThreadLocal的上下文对象存储器
* @author kong
*
*/
public class SaTokenContextForThreadLocalStorage {
/**
* 基于 ThreadLocal 的上下文
*/
static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<Box>();
/**
* 初始化 [容器]
* @param request {@link SaRequest}
* @param response {@link SaResponse}
* @param storage {@link SaStorage}
*/
public static void setBox(SaRequest request, SaResponse response, SaStorage storage) {
Box bok = new Box(request, response, storage);
boxThreadLocal.set(bok);
};
/**
* 清除 [容器]
*/
public static void clearBox() {
boxThreadLocal.remove();
};
/**
* 获取 [容器]
* @return see note
*/
public static Box getBox() {
return boxThreadLocal.get();
};
/**
* 获取 [容器], 如果为空则抛出异常
* @return see note
*/
public static Box getBoxNotNull() {
Box box = boxThreadLocal.get();
if(box == null) {
throw new SaTokenException("未成功初始化上下文");
}
return box;
};
/**
* 在 [上下文容器] 获取 [Request] 对象
*
* @return see note
*/
public static SaRequest getRequest() {
return getBoxNotNull().getRequest();
}
/**
* 在 [上下文容器] 获取 [Response] 对象
*
* @return see note
*/
public static SaResponse getResponse() {
return getBoxNotNull().getResponse();
}
/**
* 在 [上下文容器] 获取 [存储器] 对象
*
* @return see note
*/
public static SaStorage getStorage() {
return getBoxNotNull().getStorage();
}
/**
* 临时内部类,存储三个对象
* @author kong
*/
/**
* @author kong
*
*/
public static class Box {
public SaRequest request;
public SaResponse response;
public SaStorage storage;
public Box(SaRequest request, SaResponse response, SaStorage storage){
this.request = request;
this.response = response;
this.storage = storage;
}
public SaRequest getRequest() {
return request;
}
public void setRequest(SaRequest request) {
this.request = request;
}
public SaResponse getResponse() {
return response;
}
public void setResponse(SaResponse response) {
this.response = response;
}
public SaStorage getStorage() {
return storage;
}
public void setStorage(SaStorage storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Box [request=" + request + ", response=" + response + ", storage=" + storage + "]";
}
}
}

View File

@@ -13,26 +13,6 @@ public interface SaRequest {
*/
public Object getSource();
/**
* 在 [Request作用域] 里写入一个值
* @param name 键
* @param value 值
*/
public void setAttribute(String name, Object value);
/**
* 在 [Request作用域] 里获取一个值
* @param name 键
* @return 值
*/
public Object getAttribute(String name);
/**
* 在 [Request作用域] 里删除一个值
* @param name 键
*/
public void removeAttribute(String name);
/**
* 在 [请求体] 里获取一个值
* @param name 键

View File

@@ -0,0 +1,37 @@
package cn.dev33.satoken.context.model;
/**
* [存储器] 包装类
* <p> 在 Request作用域里: 存值、取值
* @author kong
*
*/
public interface SaStorage {
/**
* 获取底层源对象
* @return see note
*/
public Object getSource();
/**
* 在 [Request作用域] 里写入一个值
* @param key 键
* @param value 值
*/
public void set(String key, Object value);
/**
* 在 [Request作用域] 里获取一个值
* @param key 键
* @return 值
*/
public Object get(String key);
/**
* 在 [Request作用域] 里删除一个值
* @param key 键
*/
public void delete(String key);
}

View File

@@ -1,4 +1,4 @@
/**
* 因为不能确定最终运行的容器属于标准Servlet模型还是非Servlet模型特封装此包下的包装类进行对接
*/
package cn.dev33.satoken.context;
package cn.dev33.satoken.context.model;

View File

@@ -0,0 +1,17 @@
package cn.dev33.satoken.filter;
/**
* sa-token全局过滤器-异常处理策略
* @author kong
*
*/
public interface SaFilterErrorStrategy {
/**
* 执行方法
* @param e 异常对象
* @return 输出对象(请提前序列化)
*/
public Object run(Throwable e);
}

View File

@@ -0,0 +1,22 @@
package cn.dev33.satoken.filter;
import cn.dev33.satoken.exception.SaTokenException;
/**
* sa-token全局过滤器-异常处理策略 [默认实现]
*
* @author kong
*
*/
public class SaFilterErrorStrategyDefaultImpl implements SaFilterErrorStrategy {
/**
* 执行方法
* @throws Throwable 抛出异常
*/
@Override
public Object run(Throwable e) {
throw new SaTokenException(e);
}
}

View File

@@ -0,0 +1,16 @@
package cn.dev33.satoken.filter;
/**
* sa-token全局过滤器-认证策略
* @author kong
*
*/
public interface SaFilterStrategy {
/**
* 执行方法
* @param r 无含义参数,留作扩展
*/
public void run(Object r);
}

View File

@@ -0,0 +1,19 @@
package cn.dev33.satoken.filter;
/**
* sa-token全局过滤器-认证策略 [默认实现]
*
* @author kong
*
*/
public class SaFilterStrategyDefaultImpl implements SaFilterStrategy {
/**
* 执行验证的方法
*/
@Override
public void run(Object r) {
// default no action
}
}

View File

@@ -14,6 +14,7 @@ import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.config.SaTokenConfig;
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.dao.SaTokenDao;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
@@ -93,15 +94,15 @@ public class StpLogic {
*/
public void setTokenValue(String tokenValue, int cookieTimeout){
SaTokenConfig config = getConfig();
// 将token保存到本次Request
SaRequest request = SaTokenManager.getSaTokenContext().getRequest();
// 将token保存到[存储器]
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
// 判断是否配置了token前缀
String tokenPrefix = config.getTokenPrefix();
if(SaTokenInsideUtil.isEmpty(tokenPrefix)) {
request.setAttribute(splicingKeyJustCreatedSave(), tokenValue);
storage.set(splicingKeyJustCreatedSave(), tokenValue);
} else {
// 如果配置了token前缀则拼接上前缀一起写入
request.setAttribute(splicingKeyJustCreatedSave(), tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT + tokenValue);
storage.set(splicingKeyJustCreatedSave(), tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT + tokenValue);
}
// 注入Cookie
@@ -117,14 +118,15 @@ public class StpLogic {
*/
public String getTokenValue(){
// 0. 获取相应对象
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
SaRequest request = SaTokenManager.getSaTokenContext().getRequest();
SaTokenConfig config = getConfig();
String keyTokenName = getTokenName();
String tokenValue = null;
// 1. 尝试从request里读取
if(request.getAttribute(splicingKeyJustCreatedSave()) != null) {
tokenValue = String.valueOf(request.getAttribute(splicingKeyJustCreatedSave()));
// 1. 尝试从Storage里读取
if(storage.get(splicingKeyJustCreatedSave()) != null) {
tokenValue = String.valueOf(storage.get(splicingKeyJustCreatedSave()));
}
// 2. 尝试从请求体里面读取
if(tokenValue == null && config.getIsReadBody()){
@@ -361,7 +363,7 @@ public class StpLogic {
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 -1=永久封禁)
*/
public void disableLoginId(Object loginId, long disableTime) {
public void disable(Object loginId, long disableTime) {
SaTokenManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
}
@@ -383,6 +385,15 @@ public class StpLogic {
return SaTokenManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public void untieDisable(Object loginId) {
SaTokenManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
}
// 查询相关
/**
@@ -686,7 +697,7 @@ public class StpLogic {
// 删除[最后操作时间]
SaTokenManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
// 清除标记
SaTokenManager.getSaTokenContext().getRequest().removeAttribute((SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY));
SaTokenManager.getSaTokenContext().getStorage().delete((SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY));
}
/**
@@ -699,8 +710,8 @@ public class StpLogic {
return;
}
// 如果本次请求已经有了[检查标记], 则立即返回
SaRequest request = SaTokenManager.getSaTokenContext().getRequest();
if(request.getAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) {
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
if(storage.get(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) {
return;
}
// ------------ 验证是否已经 [临时过期]
@@ -717,7 +728,7 @@ public class StpLogic {
// --- 至此,验证已通过
// 打上[检查标记],标记一下当前请求已经通过验证,避免一次请求多次验证,造成不必要的性能消耗
request.setAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
storage.set(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
}
/**
@@ -1249,14 +1260,14 @@ public class StpLogic {
* @param loginId 指定loginId
*/
public void switchTo(Object loginId) {
SaTokenManager.getSaTokenContext().getRequest().setAttribute(splicingKeySwitch(), loginId);
SaTokenManager.getSaTokenContext().getStorage().set(splicingKeySwitch(), loginId);
}
/**
* 结束临时切换身份
*/
public void endSwitch() {
SaTokenManager.getSaTokenContext().getRequest().removeAttribute(splicingKeySwitch());
SaTokenManager.getSaTokenContext().getStorage().delete(splicingKeySwitch());
}
/**
@@ -1264,7 +1275,7 @@ public class StpLogic {
* @return 是否正处于[身份临时切换]中
*/
public boolean isSwitch() {
return SaTokenManager.getSaTokenContext().getRequest().getAttribute(splicingKeySwitch()) != null;
return SaTokenManager.getSaTokenContext().getStorage().get(splicingKeySwitch()) != null;
}
/**
@@ -1272,7 +1283,7 @@ public class StpLogic {
* @return 返回[身份临时切换]的loginId
*/
public Object getSwitchLoginId() {
return SaTokenManager.getSaTokenContext().getRequest().getAttribute(splicingKeySwitch());
return SaTokenManager.getSaTokenContext().getStorage().get(splicingKeySwitch());
}
/**
@@ -1292,7 +1303,4 @@ public class StpLogic {
}
}

View File

@@ -139,8 +139,8 @@ public class StpUtil {
* @param loginId 指定账号id
* @param disableTime 封禁时间, 单位: 秒 -1=永久封禁)
*/
public static void disableLoginId(Object loginId, long disableTime) {
stpLogic.disableLoginId(loginId, disableTime);
public static void disable(Object loginId, long disableTime) {
stpLogic.disable(loginId, disableTime);
}
/**
@@ -161,6 +161,13 @@ public class StpUtil {
return stpLogic.getDisableTime(loginId);
}
/**
* 解封指定账号
* @param loginId 账号id
*/
public static void untieDisable(Object loginId) {
stpLogic.untieDisable(loginId);
}
// 查询相关

View File

@@ -84,4 +84,9 @@ public class SaTokenConsts {
*/
public static final String TOKEN_CONNECTOR_CHAT = " ";
/**
* 切面、拦截器、过滤器等各种组件的注册优先级顺序
*/
public static final int ASSEMBLY_ORDER = -100;
}

View File

@@ -46,17 +46,17 @@
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<dependency>
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependency> -->
<!-- 提供redis连接池 -->
<dependency>
<!-- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependency> -->
<!-- sa-token整合SpringAOP实现注解鉴权 -->
<!-- <dependency>

View File

@@ -5,6 +5,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
/**
* sa-token整合SpringBoot 示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenDemoApplication {

View File

@@ -6,13 +6,14 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
/**
* sa-token代码方式进行配置
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
public class SaTokenConfiguration implements WebMvcConfigurer {
// 注册sa-token的拦截器打开注解式鉴权功能
@Override

12
sa-token-demo-webflux/.gitignore vendored Normal file
View File

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

View File

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

View File

@@ -0,0 +1,32 @@
package com.pj;
//import org.springframework.boot.SpringApplication;
//import org.springframework.boot.autoconfigure.SpringBootApplication;
//import org.springframework.context.annotation.Bean;
//import org.springframework.http.MediaType;
//import org.springframework.web.reactive.function.server.RequestPredicates;
//import org.springframework.web.reactive.function.server.RouterFunction;
//import org.springframework.web.reactive.function.server.RouterFunctions;
//import org.springframework.web.reactive.function.server.ServerRequest;
//import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
/**
* sa-token整合webflux 示例
* @author kong
*
*/
@SpringBootApplication
public class SaTokenWebfluxDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenWebfluxDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());
}
}

View File

@@ -0,0 +1,57 @@
package com.pj.satoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaFilterStrategy;
import cn.dev33.satoken.reactor.context.SaReactorFilter;
import cn.dev33.satoken.router.SaRouterUtil;
import cn.dev33.satoken.stp.StpUtil;
/**
* [Sa-Token 权限认证] 配置类
* @author kong
*
*/
@Configuration
public class SaTokenConfiguration {
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter();
}
/**
* 注册 [sa-token全局过滤器-认证策略]
*/
@Bean
public SaFilterStrategy getSaFilterStrategy() {
return r -> {
System.out.println("---------- 进入sa-token全局过滤器 -----------");
SaRouterUtil.match("/test/test", () -> StpUtil.checkLogin());
// SaRouterUtil.match("/user/**", () -> StpUtil.checkPermission("user"));
// SaRouterUtil.match("/admin/**", () -> StpUtil.checkPermission("admin"));
// SaRouterUtil.match("/goods/**", () -> StpUtil.checkPermission("goods"));
// SaRouterUtil.match("/orders/**", () -> StpUtil.checkPermission("orders"));
// SaRouterUtil.match("/notice/**", () -> StpUtil.checkPermission("notice"));
// SaRouterUtil.match("/comment/**", () -> StpUtil.checkPermission("comment"));
};
}
/**
* 注册 [sa-token全局过滤器-异常处理策略]
*/
@Bean
public SaFilterErrorStrategy getSaFilterErrorStrategy() {
return e -> AjaxJson.getError(e.getMessage());
}
}

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
package com.pj.test;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
} else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
} else if(e instanceof DisableLoginException) { // 如果是被封禁异常
DisableLoginException ee = (DisableLoginException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
} else { // 普通异常, 输出500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
}
}

View File

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

View File

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

View File

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

View File

@@ -65,6 +65,14 @@
alias: {
'/.*/_sidebar.md': '/_sidebar.md'
},
// tab选项卡
tabs: {
persist : true, // 是否在刷新页面时重置选项卡
sync : true, // 页面上的多个tab是否同步切换
theme : 'classic', // 主题:'classic', 'material', false
tabComments: true, // 用注释来标注选项卡标题,例如:<!-- tab:SpringBoot -->
tabHeadings: true // 用标题+粗体来定制选项卡
},
plugins: [ // 自定义插件
function(hook, vm) {
// 解析之后执行
@@ -102,6 +110,7 @@
<script src="https://unpkg.zhimg.com/docsify-copy-code@2.1.0/dist/docsify-copy-code.min.js"></script>
<script src="https://unpkg.zhimg.com/prismjs@1.19.0/components/prism-java.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="https://unpkg.zhimg.com/docsify-tabs@1.4.4"></script>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>

View File

@@ -31,13 +31,16 @@ StpUtil.logoutByTokenValue("xxxx-xxxx-xxxx-xxxx-xxxx");
// 封禁指定账号
// 参数一账号id
// 参数二:封禁时长,单位:秒 (86400秒=1天此值为-1时代表永久封禁)
StpUtil.disableLoginId(10001, 86400);
StpUtil.disable(10001, 86400);
// 获取指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
StpUtil.isDisable(10001);
// 获取指定账号剩余封禁时间,单位:秒
StpUtil.getDisableTime(10001);
// 解除封禁
StpUtil.untieDisable(10001);
```

View File

@@ -51,6 +51,7 @@
<div class="btn-box">
<a href="https://github.com/dromara/sa-token" target="_blank">GitHub</a>
<a href="https://gitee.com/dromara/sa-token" target="_blank">码云</a>
<a href="https://jq.qq.com/?_wv=1027&k=45H977HM" target="_blank">加QQ群</a>
<!-- <a href="http://sa-app.dev33.cn/wall.html?name=sa-token" target="_blank">需求墙</a> -->
<a href="doc/index.html" target="_self" class="doc-btn">开发文档</a>
</div>
@@ -253,7 +254,7 @@
<h3>联系我们</h3>
<ul class="list-unstyle">
<!-- <li>电话:<a href="tel:123;">123</a></li> -->
<li>QQ群 <a href="https://jq.qq.com/?_wv=1027&k=5DHN5Ib" target="_blank">1002350610</a></li>
<li>QQ群 <a href="https://jq.qq.com/?_wv=1027&k=45H977HM" target="_blank">1002350610</a></li>
<li>邮箱:<a href="javascript: alert('暂无');">暂无</a></li>
<li>联系:<a href="javascript: alert('暂无');">暂无</a></li>
</ul>

View File

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

View File

@@ -0,0 +1,63 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.15.2</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-reactor-spring-boot-starter</name>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<description>springboot reactor integrate sa-token</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.0.0.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- reactor-core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.0.0.RELEASE</version>
<optional>true</optional>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.0.0.RELEASE</version>
</dependency> -->
</dependencies>
</project>

View File

@@ -0,0 +1,63 @@
package cn.dev33.satoken.reactor.context;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.util.SaTokenConsts;
import reactor.core.publisher.Mono;
/**
* Reactor全局过滤器
* @author kong
*
*/
@Order(SaTokenConsts.ASSEMBLY_ORDER)
public class SaReactorFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ---------- 全局认证处理
try {
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContent(exchange);
// 执行全局过滤器
SaTokenManager.getSaFilterStrategy().run(null);
} catch (Throwable e) {
// 1. 获取异常处理策略结果
Object result = SaTokenManager.getSaFilterErrorStrategy().run(e);
String resultString = String.valueOf(result);
// 2. 写入输出流
if(exchange.getResponse().getHeaders().getFirst("Content-Type") == null) {
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
}
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(resultString.getBytes())));
} finally {
// 清除上下文
SaReactorSyncHolder.clearContent();
}
// ---------- 执行
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContent(exchange);
// 执行
return chain.filter(exchange).subscriberContext(ctx -> {
// 写入全局上下文 (异步)
ctx = ctx.put(SaReactorHolder.CONTEXT_KEY, exchange);
return ctx;
}).doFinally(r -> {
// 清除上下文
SaReactorSyncHolder.clearContent();
});
}
}

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import cn.dev33.satoken.context.model.SaRequest;
/**
* Request for Reactor
* @author kong
*
*/
public class SaRequestForReactor implements SaRequest {
/**
* 底层Request对象
*/
ServerHttpRequest request;
/**
* 实例化
* @param request request对象
*/
public SaRequestForReactor(ServerHttpRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return request;
}
/**
* 在 [请求体] 里获取一个值
*/
@Override
public String getParameter(String name) {
return request.getQueryParams().getFirst(name);
}
/**
* 在 [请求头] 里获取一个值
*/
@Override
public String getHeader(String name) {
return request.getHeaders().getFirst(name);
}
/**
* 在 [Cookie作用域] 里获取一个值
*/
@Override
public String getCookieValue(String name) {
HttpCookie cookie = request.getCookies().getFirst(name);
if(cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* 返回当前请求的URL
*/
@Override
public String getRequestURI() {
return request.getURI().getPath();
}
}

View File

@@ -0,0 +1,74 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseCookie.ResponseCookieBuilder;
import org.springframework.http.server.reactive.ServerHttpResponse;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* Response for Reactor
* @author kong
*
*/
public class SaResponseForReactor implements SaResponse {
/**
* 底层Response对象
*/
ServerHttpResponse response;
/**
* 实例化
* @param response response对象
*/
public SaResponseForReactor(ServerHttpResponse response) {
this.response = response;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return response;
}
/**
* 删除指定Cookie
*/
@Override
public void deleteCookie(String name) {
addCookie(name, null, null, null, 0);
}
/**
* 写入指定Cookie
*/
@Override
public void addCookie(String name, String value, String path, String domain, int timeout) {
// 构建CookieBuilder
ResponseCookieBuilder builder = ResponseCookie.from(name, value)
.domain(domain)
.path(path)
.maxAge(timeout)
;
// set path
if(SaTokenInsideUtil.isEmpty(path) == true) {
path = "/";
}
builder.path(path);
// set domain
if(SaTokenInsideUtil.isEmpty(domain) == false) {
builder.domain(domain);
}
// 写入Cookie
response.addCookie(builder.build());
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,138 @@
package cn.dev33.satoken.reactor.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaFilterStrategy;
import cn.dev33.satoken.stp.StpInterface;
/**
* 利用spring的自动装配来加载开发者重写的Bean
*
* @author kong
*
*/
@Component
public class SaTokenSpringAutowired {
/**
* 获取配置Bean
*
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "spring.sa-token")
public SaTokenConfig getSaTokenConfig() {
return new SaTokenConfig();
}
/**
* 注入配置Bean
*
* @param saTokenConfig 配置对象
*/
@Autowired
public void setConfig(SaTokenConfig saTokenConfig) {
SaTokenManager.setConfig(saTokenConfig);
}
/**
* 注入持久化Bean
*
* @param saTokenDao SaTokenDao对象
*/
@Autowired(required = false)
public void setSaTokenDao(SaTokenDao saTokenDao) {
SaTokenManager.setSaTokenDao(saTokenDao);
}
/**
* 注入权限认证Bean
*
* @param stpInterface StpInterface对象
*/
@Autowired(required = false)
public void setStpInterface(StpInterface stpInterface) {
SaTokenManager.setStpInterface(stpInterface);
}
/**
* 注入框架行为Bean
*
* @param saTokenAction SaTokenAction对象
*/
@Autowired(required = false)
public void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.setSaTokenAction(saTokenAction);
}
/**
* 获取容器交互Bean (ThreadLocal版)
*
* @return 容器交互Bean (ThreadLocal版)
*/
@Bean
public SaTokenContext getSaTokenContext() {
return new SaTokenContextForThreadLocal() {
/**
* 重写路由匹配方法
*/
@Override
public boolean matchPath(String pattern, String path) {
return SaPathMatcherHolder.getPathMatcher().match(pattern, path);
}
};
}
/**
* 注入容器交互Bean
*
* @param saTokenContext SaTokenContext对象
*/
@Autowired
public void setSaTokenContext(SaTokenContext saTokenContext) {
SaTokenManager.setSaTokenContext(saTokenContext);
}
/**
* 注入[sa-token全局过滤器-认证策略]
*
* @param strategy see note
*/
@Autowired(required = false)
public void setSaFilterStrategy(SaFilterStrategy strategy) {
SaTokenManager.setSaFilterStrategy(strategy);
}
/**
* 注入[sa-token全局过滤器-异常处理策略]
*
* @param errorStrategy see note
*/
@Autowired(required = false)
public void setSaFilterErrorStrategy(SaFilterErrorStrategy errorStrategy) {
SaTokenManager.setSaFilterErrorStrategy(errorStrategy);
}
/**
* 利用自动匹配特性获取SpringMVC框架内部使用的路由匹配器
*
* @param pathMatcher 要设置的 pathMatcher
*/
@Autowired(required = false)
public void setPathMatcher(PathMatcher pathMatcher) {
SaPathMatcherHolder.setPathMatcher(pathMatcher);
}
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.reactor.spring.SaTokenSpringAutowired

View File

@@ -16,7 +16,7 @@
<description>sa-token authentication by Sservlet API</description>
<dependencies>
<!-- sa-token-spring-boot-starter -->
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
@@ -27,6 +27,7 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<optional>true</optional>
</dependency>
</dependencies>

View File

@@ -1,4 +1,4 @@
package cn.dev33.satoken.context.model.servlet;
package cn.dev33.satoken.servlet.model;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -33,30 +33,6 @@ public class SaRequestForServlet implements SaRequest {
return request;
}
/**
* [Request作用域] 里写入一个值
*/
@Override
public void setAttribute(String name, Object value) {
request.setAttribute(name, value);
}
/**
* [Request作用域] 里获取一个值
*/
@Override
public Object getAttribute(String name) {
return request.getAttribute(name);
}
/**
* [Request作用域] 里删除一个值
*/
@Override
public void removeAttribute(String name) {
request.removeAttribute(name);
}
/**
* [请求体] 里获取一个值
*/

View File

@@ -1,4 +1,4 @@
package cn.dev33.satoken.context.model.servlet;
package cn.dev33.satoken.servlet.model;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* sa-token 基于 Spring Aop 的注解鉴权
@@ -18,14 +19,9 @@ import cn.dev33.satoken.stp.StpUtil;
*/
@Aspect
@Component
@Order(SaCheckAspect.aspectOrder)
@Order(SaTokenConsts.ASSEMBLY_ORDER)
public class SaCheckAspect {
/**
* 切面执行顺序
*/
public static final int aspectOrder = -100;
/**
* 构建
*/

View File

@@ -1,25 +0,0 @@
package cn.dev33.satoken.autowired;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 将此注解加到springboot启动类上即可完成sa-token与springboot的集成
* <p>注: v1.7版本以上已不再需要此注解直接引入sa-token-spring-boot-starter依赖即可
* <p>请直接忽略此注解
* @author kong
*
*/
@Documented
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@Import({SaTokenSpringAutowired.class})
public @interface SaTokenSetup {
}

View File

@@ -5,9 +5,9 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import cn.dev33.satoken.context.model.servlet.SaRequestForServlet;
import cn.dev33.satoken.context.model.servlet.SaResponseForServlet;
import cn.dev33.satoken.router.SaRouteFunction;
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
import cn.dev33.satoken.servlet.model.SaResponseForServlet;
import cn.dev33.satoken.stp.StpUtil;
/**

View File

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

View File

@@ -1,13 +1,12 @@
package cn.dev33.satoken.spring;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
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.servlet.SaRequestForServlet;
import cn.dev33.satoken.context.model.servlet.SaResponseForServlet;
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;
/**
* sa-token 对Cookie的相关操作 接口实现类
@@ -34,27 +33,11 @@ public class SaTokenContextForSpring implements SaTokenContext {
}
/**
* 路由匹配器
* 获取当前请求的 [存储器] 对象
*/
private static PathMatcher pathMatcher;
/**
* 获取路由匹配器
* @return 路由匹配器
*/
public static PathMatcher getPathMatcher() {
if(pathMatcher == null) {
pathMatcher = new AntPathMatcher();
}
return pathMatcher;
}
/**
* 写入路由匹配器
* @param pathMatcher 路由匹配器
*/
public static void setPathMatcher(PathMatcher pathMatcher) {
SaTokenContextForSpring.pathMatcher = pathMatcher;
@Override
public SaStorage getStorage() {
return new SaStorageForServlet(SpringMVCUtil.getRequest());
}
/**
@@ -62,7 +45,9 @@ public class SaTokenContextForSpring implements SaTokenContext {
*/
@Override
public boolean matchPath(String pattern, String path) {
return getPathMatcher().match(pattern, path);
return SaPathMatcherHolder.getPathMatcher().match(pattern, path);
}
}

View File

@@ -1,4 +1,4 @@
package cn.dev33.satoken.autowired;
package cn.dev33.satoken.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -11,7 +11,8 @@ import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.spring.SaTokenContextForSpring;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.filter.SaFilterStrategy;
import cn.dev33.satoken.stp.StpInterface;
/**
@@ -94,6 +95,26 @@ public class SaTokenSpringAutowired {
SaTokenManager.setSaTokenContext(saTokenContext);
}
/**
* 注入[sa-token全局过滤器-认证策略]
*
* @param strategy see note
*/
@Autowired(required = false)
public void setSaFilterStrategy(SaFilterStrategy strategy) {
SaTokenManager.setSaFilterStrategy(strategy);
}
/**
* 注入[sa-token全局过滤器-异常处理策略]
*
* @param errorStrategy see note
*/
@Autowired(required = false)
public void setSaFilterErrorStrategy(SaFilterErrorStrategy errorStrategy) {
SaTokenManager.setSaFilterErrorStrategy(errorStrategy);
}
/**
* 利用自动匹配特性获取SpringMVC框架内部使用的路由匹配器
*
@@ -101,7 +122,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setPathMatcher(PathMatcher pathMatcher) {
SaTokenContextForSpring.setPathMatcher(pathMatcher);
SaPathMatcherHolder.setPathMatcher(pathMatcher);
}

View File

@@ -1 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.autowired.SaTokenSpringAutowired
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.spring.SaTokenSpringAutowired