diff --git a/sa-token-starter/pom.xml b/sa-token-starter/pom.xml index 6456d0a00dfc112b8519cc77ca054cb2f84b0b93..8f17cade9fc0267968143bed3ae16fb0563f980f 100644 --- a/sa-token-starter/pom.xml +++ b/sa-token-starter/pom.xml @@ -29,6 +29,7 @@ sa-token-jboot-plugin sa-token-jfinal-plugin sa-token-loveqq-boot-starter + sa-token-reactor-spring-cloud3-starter \ No newline at end of file diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/pom.xml b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd41a205f4851766b67d4afb13ea8f0fd69bc4dc --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + cn.dev33 + sa-token-starter + ${revision} + ../pom.xml + + + sa-token-reactor-spring-cloud3-starter + + + 3.0.1 + 4.2.0 + + + + + + org.springframework.boot + spring-boot-starter + true + + + + + org.springframework + spring-web + true + + + + + io.projectreactor + reactor-core + + + + + com.fasterxml.jackson.core + jackson-databind + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + cn.dev33 + sa-token-spring-boot-autoconfig + + + + org.springframework.cloud + spring-cloud-starter-gateway + true + + + + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + ${spring-cloud-gateway} + + + + org.springframework.boot + spring-boot-starter + ${springboot3.version} + + + + org.springframework + spring-web + 6.2.5 + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + ${springboot3.version} + + + + + + \ No newline at end of file diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..6f81609c624a6ceef89250a3f549a8213e665165 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java @@ -0,0 +1,98 @@ +/* + * 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.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.19.0 + */ +public class SaReactorHolder { + + /** + * ServerWebExchange key + */ + public static final String EXCHANGE_KEY = "SA_REACTOR_EXCHANGE_KEY"; + + /** + * WebFilterChain key + */ + public static final String CHAIN_KEY = "SA_REACTOR__CHAIN_KEY"; + + /** + * 在流式上下文写入 ServerWebExchange + * @param ctx 必填 + * @param exchange 必填 + * @param chain 非必填 + * @return / + */ + public static Context setContext(Context ctx, ServerWebExchange exchange, WebFilterChain chain) { + return ctx + .put(EXCHANGE_KEY, exchange) + .put(CHAIN_KEY, chain); + } + + /** + * 在流式上下文获取 ServerWebExchange + * @param ctx / + * @return / + */ + 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 getMonoExchange() { + return Mono.deferContextual(ctx -> Mono.just(getExchange(ctx))); + } + + /** + * 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文 + * + * @return / + */ + public static Mono sync(SaRetGenericFunction fun) { + return Mono.deferContextual(ctx -> { + try { + SaReactorSyncHolder.setContext(ctx.get(EXCHANGE_KEY)); + return Mono.just(fun.run()); + } finally { + SaReactorSyncHolder.clearContext(); + } + }); + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorSyncHolder.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorSyncHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..a2c915c1e9365dbb019750efa829a323ca354d75 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorSyncHolder.java @@ -0,0 +1,78 @@ +/* + * 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.context; + +import cn.dev33.satoken.SaManager; +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.model.SaTokenContextModelBox; +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.19.0 + */ +public class SaReactorSyncHolder { + + /** + * 在同步上下文写入 ServerWebExchange + * @param exchange / + */ + public static void setContext(ServerWebExchange exchange) { + SaRequest request = new SaRequestForReactor(exchange.getRequest()); + SaResponse response = new SaResponseForReactor(exchange.getResponse()); + SaStorage storage = new SaStorageForReactor(exchange); + SaManager.getSaTokenContext().setContext(request, response, storage); + } + + /** + * 在同步上下文清除 ServerWebExchange + */ + public static void clearContext() { + SaManager.getSaTokenContext().clearContext(); + } + + /** + * 在同步上下文获取 ServerWebExchange + * @return / + */ + public static ServerWebExchange getExchange() { + SaTokenContextModelBox box = SaManager.getSaTokenContext().getModelBox(); + return (ServerWebExchange)box.getStorage().getSource(); + } + + /** + * 将 exchange 写入到同步上下文中,并执行一段代码,执行完毕清除上下文 + * @param exchange / + * @param fun / + */ + public static R setContext(ServerWebExchange exchange, SaRetGenericFunction fun) { + try { + setContext(exchange); + return fun.run(); + } finally { + clearContext(); + } + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaFirewallCheckFilterForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaFirewallCheckFilterForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..da6d6859bad782f0ccc923d9f3497fe8357aa853 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaFirewallCheckFilterForReactor.java @@ -0,0 +1,74 @@ +/* + * 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.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; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +/** + * 防火墙校验过滤器 (Reactor版) + * + * @author click33 + * @since 1.37.0 + */ +@Order(SaTokenConsts.FIREWALL_CHECK_FILTER_ORDER) +public class SaFirewallCheckFilterForReactor implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + + SaRequestForReactor saRequest = new SaRequestForReactor(exchange.getRequest()); + SaResponseForReactor saResponse = new SaResponseForReactor(exchange.getResponse()); + + try { + SaReactorSyncHolder.setContext(exchange); + SaFirewallStrategy.instance.check.execute(saRequest, saResponse, exchange); + } + catch (StopMatchException ignored) {} + catch (BackResultException e) { + return SaReactorOperateUtil.writeResult(exchange, e.getMessage()); + } + // FirewallCheckException 异常则交由异常处理策略处理 + catch (FirewallCheckException e) { + if(SaFirewallStrategy.instance.checkFailHandle == null) { + return SaReactorOperateUtil.writeResult(exchange, e.getMessage()); + } else { + SaFirewallStrategy.instance.checkFailHandle.run(e, saRequest, saResponse, null); + return Mono.empty(); + } + } + finally { + SaReactorSyncHolder.clearContext(); + } + // 更多异常则不处理,交由 Web 框架处理 + + // 向下执行 + return chain.filter(exchange); + } + +} \ No newline at end of file diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..92670f5f9e7b816cf6d9c6f42f0dfc6883e88930 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java @@ -0,0 +1,148 @@ +/* + * 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.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.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.reactor.util.SaReactorOperateUtil; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.util.SaTokenConsts; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Reactor 全局鉴权过滤器 + *

+ * 默认优先级为 -100,尽量保证在其它过滤器之前执行 + *

+ * + * @author click33 + * @since 1.34.0 + */ +@Order(SaTokenConsts.ASSEMBLY_ORDER) +public class SaReactorFilter implements SaFilter, GlobalFilter { + + // ------------------------ 设置此过滤器 拦截 & 放行 的路由 + + /** + * 拦截路由 + */ + public List includeList = new ArrayList<>(); + + /** + * 放行路由 + */ + public List excludeList = new ArrayList<>(); + + @Override + public SaReactorFilter addInclude(String... paths) { + includeList.addAll(Arrays.asList(paths)); + return this; + } + + @Override + public SaReactorFilter addExclude(String... paths) { + excludeList.addAll(Arrays.asList(paths)); + return this; + } + + @Override + public SaReactorFilter setIncludeList(List pathList) { + includeList = pathList; + return this; + } + + @Override + public SaReactorFilter setExcludeList(List pathList) { + excludeList = pathList; + return this; + } + + + // ------------------------ 钩子函数 + + /** + * 认证函数:每次请求执行 + */ + public SaFilterAuthStrategy auth = r -> {}; + + /** + * 异常处理函数:每次[认证函数]发生异常时执行此函数 + */ + public SaFilterErrorStrategy error = e -> { + throw new SaTokenException(e); + }; + + /** + * 前置函数:在每次[认证函数]之前执行 + * 注意点:前置认证函数将不受 includeList 与 excludeList 的限制,所有路由的请求都会进入 beforeAuth + */ + public SaFilterAuthStrategy beforeAuth = r -> {}; + + @Override + public SaReactorFilter setAuth(SaFilterAuthStrategy auth) { + this.auth = auth; + return this; + } + + @Override + public SaReactorFilter setError(SaFilterErrorStrategy error) { + this.error = error; + return this; + } + + @Override + public SaReactorFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) { + this.beforeAuth = beforeAuth; + return this; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // ---------- 全局认证处理 + try { + SaReactorSyncHolder.setContext(exchange); + beforeAuth.run(null); + SaRouter.match(includeList).notMatch(excludeList).check(r -> auth.run(null)); + } + 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(); + } + + return chain.filter(exchange); + } +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenContextFilterForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenContextFilterForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..279368372771ad65671577f62a6aa8292541acfc --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenContextFilterForReactor.java @@ -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 filter(ServerWebExchange exchange, WebFilterChain chain) { + return chain.filter(exchange) + .contextWrite(ctx -> SaReactorHolder.setContext(ctx, exchange, chain)) + .doFinally(r -> { + // 在流式上下文中保存的数据会随着流式操作的结束而销毁,所以此处无需手动清除数据 + }); + } + +} \ No newline at end of file diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenCorsFilterForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenCorsFilterForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..65a2b985ef341ec3f3d9b18ea5fbdfe1d7a04722 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaTokenCorsFilterForReactor.java @@ -0,0 +1,60 @@ +/* + * 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.context.SaHolder; +import cn.dev33.satoken.context.model.SaTokenContextModelBox; +import cn.dev33.satoken.exception.BackResultException; +import cn.dev33.satoken.exception.StopMatchException; +import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.reactor.util.SaReactorOperateUtil; +import cn.dev33.satoken.strategy.SaStrategy; +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; + +/** + * CORS 跨域策略过滤器 (基于 Reactor) + * + * @author click33 + * @since 1.42.0 + */ +@Order(SaTokenConsts.CORS_FILTER_ORDER) +public class SaTokenCorsFilterForReactor implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + + try { + SaReactorSyncHolder.setContext(exchange); + SaTokenContextModelBox box = SaHolder.getContext().getModelBox(); + SaStrategy.instance.corsHandle.execute(box.getRequest(), box.getResponse(), box.getStorage()); + } + catch (StopMatchException ignored) {} + catch (BackResultException e) { + return SaReactorOperateUtil.writeResult(exchange, e.getMessage()); + } + finally { + SaReactorSyncHolder.clearContext(); + } + + return chain.filter(exchange); + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..ce2e42750566b6863d28405e0efc02d983d9a825 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -0,0 +1,196 @@ +/* + * 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.model; + + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.application.ApplicationInfo; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.reactor.context.SaReactorHolder; +import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.util.SaFoxUtil; +import org.springframework.http.HttpCookie; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilterChain; + +import java.util.Collection; +import java.util.Map; + +/** + * 对 SaRequest 包装类的实现(Reactor 响应式编程版) + * + * @author click33 + * @since 1.34.0 + */ +public class SaRequestForReactor implements SaRequest { + + /** + * 底层Request对象 + */ + protected ServerHttpRequest request; + + /** + * 实例化 + * @param request request对象 + */ + public SaRequestForReactor(ServerHttpRequest request) { + this.request = request; + } + + /** + * 获取底层源对象 + */ + @Override + public Object getSource() { + return request; + } + + /** + * 在 [请求体] 里获取一个值 + */ + @Override + public String getParam(String name) { + return request.getQueryParams().getFirst(name); + } + + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public Collection getParamNames(){ + return request.getQueryParams().keySet(); + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return request.getQueryParams().toSingleValueMap(); + } + + /** + * 在 [请求头] 里获取一个值 + */ + @Override + public String getHeader(String name) { + return request.getHeaders().getFirst(name); + } + + /** + * 在 [Cookie作用域] 里获取一个值 + */ + @Override + public String getCookieValue(String name) { + return getCookieLastValue(name); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (第一个此名称的) + */ + @Override + public String getCookieFirstValue(String name){ + HttpCookie cookie = request.getCookies().getFirst(name); + if(cookie == null) { + return null; + } + return cookie.getValue(); + } + + /** + * 在 [ Cookie作用域 ] 里获取一个值 (最后一个此名称的) + * @param name 键 + * @return 值 + */ + @Override + public String getCookieLastValue(String name){ + String value = null; + String cookieStr = getHeader("Cookie"); + if(SaFoxUtil.isNotEmpty(cookieStr)) { + String[] cookieItems = cookieStr.split(";"); + for (String item : cookieItems) { + String[] kv = item.split("="); + if (kv.length == 2) { + if (kv[0].trim().equals(name)) { + value = kv[1].trim(); + } + } + } + } + return value; + + // 此种写法无法获取到最后一个 Cookie,WebFlux 底层代码应该是有bug,前端提交多个同名Cookie时只能解析出第一个来 + // List cookies = request.getCookies().get(name); + // if(cookies.isEmpty()) { + // return null; + // } + // return cookies.get(cookies.size() - 1).getValue(); + } + + /** + * 返回当前请求path (不包括上下文名称) + */ + @Override + public String getRequestPath() { + return ApplicationInfo.cutPathPrefix(request.getPath().toString()); + } + + /** + * 返回当前请求的url,例:http://xxx.com/test + * @return see note + */ + public String getUrl() { + String currDomain = SaManager.getConfig().getCurrDomain(); + if( ! SaFoxUtil.isEmpty(currDomain)) { + return currDomain + this.getRequestPath(); + } + return request.getURI().toString(); + } + + /** + * 返回当前请求的类型 + */ + @Override + public String getMethod() { + return request.getMethod().name(); + } + + /** + * 查询请求 host + */ + @Override + public String getHost() { + return request.getURI().getHost(); + } + + /** + * 转发请求 + */ + @Override + public Object forward(String path) { + ServerWebExchange exchange = SaReactorSyncHolder.getExchange(); + WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY); + + ServerHttpRequest newRequest = request.mutate().path(path).build(); + ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); + + return chain.filter(newExchange); + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaResponseForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaResponseForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..6fb5ac8687d3818b9d4fbf08f1de7cd710cbd30b --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaResponseForReactor.java @@ -0,0 +1,93 @@ +/* + * 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.model; + +import java.net.URI; + +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; + +import cn.dev33.satoken.context.model.SaResponse; + +/** + * 对 SaResponse 包装类的实现(Reactor 响应式编程版) + * + * @author click33 + * @since 1.34.0 + */ +public class SaResponseForReactor implements SaResponse { + + /** + * 底层Response对象 + */ + protected ServerHttpResponse response; + + /** + * 实例化 + * @param response response对象 + */ + public SaResponseForReactor(ServerHttpResponse response) { + this.response = response; + } + + /** + * 获取底层源对象 + */ + @Override + public Object getSource() { + return response; + } + + /** + * 设置响应状态码 + */ + @Override + public SaResponse setStatus(int sc) { + response.setStatusCode(HttpStatus.valueOf(sc)); + return this; + } + + /** + * 在响应头里写入一个值 + */ + @Override + public SaResponse setHeader(String name, String value) { + response.getHeaders().set(name, value); + return this; + } + + /** + * 在响应头里添加一个值 + * @param name 名字 + * @param value 值 + * @return 对象自身 + */ + public SaResponse addHeader(String name, String value) { + response.getHeaders().add(name, value); + return this; + } + + /** + * 重定向 + */ + @Override + public Object redirect(String url) { + response.setStatusCode(HttpStatus.FOUND); + response.getHeaders().setLocation(URI.create(url)); + return null; + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaStorageForReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaStorageForReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..55e35ea89a21ed916d43344ae504e4120baee8a7 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaStorageForReactor.java @@ -0,0 +1,77 @@ +/* + * 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.model; + +import org.springframework.web.server.ServerWebExchange; + +import cn.dev33.satoken.context.model.SaStorage; + +/** + * 对 SaStorage 包装类的实现(Reactor 响应式编程版) + * + * @author click33 + * @since 1.34.0 + */ +public class SaStorageForReactor implements SaStorage { + + /** + * 底层 ServerWebExchange 对象 + */ + protected ServerWebExchange exchange; + + /** + * 实例化 + * @param exchange exchange对象 + */ + public SaStorageForReactor(ServerWebExchange exchange) { + this.exchange = exchange; + } + + /** + * 获取底层源对象 + */ + @Override + public Object getSource() { + return exchange; + } + + /** + * 在 [Request作用域] 里写入一个值 + */ + @Override + public SaStorageForReactor set(String key, Object value) { + exchange.getAttributes().put(key, value); + return this; + } + + /** + * 在 [Request作用域] 里获取一个值 + */ + @Override + public Object get(String key) { + return exchange.getAttributes().get(key); + } + + /** + * 在 [Request作用域] 里删除一个值 + */ + @Override + public SaStorageForReactor delete(String key) { + exchange.getAttributes().remove(key); + return this; + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/package-info.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..df78335d6f7b36ec599cb6b50bba74dbbd5d8a01 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +/** + * Sa-Token 集成 Reactor 响应式编程的各个组件 + */ +package cn.dev33.satoken.reactor; \ No newline at end of file diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextForSpringReactor.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextForSpringReactor.java new file mode 100644 index 0000000000000000000000000000000000000000..1b4e785a3341078310ea3bfae3839f93faf99e11 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextForSpringReactor.java @@ -0,0 +1,30 @@ +/* + * 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.spring; + +import cn.dev33.satoken.context.SaTokenContextForThreadLocal; + +/** + *

此为低版本(<1.42.0) 的上下文处理方案,仅做留档,如无必要请勿使用

+ * + * Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ] ,基于 SaTokenContextForThreadLocal 定制 + * + * @author click33 + * @since 1.33.0 + */ +public class SaTokenContextForSpringReactor extends SaTokenContextForThreadLocal { + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextRegister.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextRegister.java new file mode 100644 index 0000000000000000000000000000000000000000..559be296a8331ed3984e9eae7bb06531a4d954a5 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenContextRegister.java @@ -0,0 +1,70 @@ +/* + * 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.spring; + +import cn.dev33.satoken.reactor.filter.SaFirewallCheckFilterForReactor; +import cn.dev33.satoken.reactor.filter.SaTokenContextFilterForReactor; +import cn.dev33.satoken.reactor.filter.SaTokenCorsFilterForReactor; +import cn.dev33.satoken.spring.pathmatch.SaPathPatternParserUtil; +import cn.dev33.satoken.strategy.SaStrategy; +import org.springframework.context.annotation.Bean; + +/** + * 注册 Sa-Token 所需要的 Bean + * + * @author click33 + * @since 1.34.0 + */ +public class SaTokenContextRegister { + + public SaTokenContextRegister() { + // 重写路由匹配算法 + SaStrategy.instance.routeMatcher = (pattern, path) -> { + return SaPathPatternParserUtil.match(pattern, path); + }; + } + + /** + * 上下文过滤器 + * + * @return / + */ + @Bean + public SaTokenContextFilterForReactor saTokenContextFilterForServlet() { + return new SaTokenContextFilterForReactor(); + } + + /** + * CORS 跨域策略过滤器 + * + * @return / + */ + @Bean + public SaTokenCorsFilterForReactor saTokenCorsFilterForReactor() { + return new SaTokenCorsFilterForReactor(); + } + + /** + * 防火墙过滤器 + * + * @return / + */ + @Bean + public SaFirewallCheckFilterForReactor saFirewallCheckFilterForReactor() { + return new SaFirewallCheckFilterForReactor(); + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/util/SaReactorOperateUtil.java b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/util/SaReactorOperateUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..706501bf568304d1f95730c2190d809b42b80c71 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/java/cn/dev33/satoken/reactor/util/SaReactorOperateUtil.java @@ -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 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()))); + } + +} diff --git a/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000000000000000000000000000000000..325771974bbf8efcc8015e0b2d0999792b6c72f1 --- /dev/null +++ b/sa-token-starter/sa-token-reactor-spring-cloud3-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cn.dev33.satoken.reactor.spring.SaTokenContextRegister \ No newline at end of file