修复路由拦截鉴权可被绕过的问题 fix #515

This commit is contained in:
click33 2023-10-16 16:02:19 +08:00
parent f2416a6175
commit 954efeb732
33 changed files with 688 additions and 79 deletions

View File

@ -0,0 +1,45 @@
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.application;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 应用全局信息
*
* @author click33
* @since 1.31.0
*/
public class ApplicationInfo {
/**
* 应用前缀
*/
public static String routePrefix;
/**
* 为指定 path 裁剪掉 routePrefix 前缀
* @param path 指定 path
* @return /
*/
public static String cutPathPrefix(String path) {
if(! SaFoxUtil.isEmpty(routePrefix) && ! routePrefix.equals("/") && path.startsWith(routePrefix)){
path = path.substring(routePrefix.length());
}
return path;
}
}

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ package cn.dev33.satoken.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.strategy.*;
import cn.dev33.satoken.session.SaSession;
@ -329,6 +330,49 @@ public final class SaStrategy {
return new StpLogic(loginType);
};
/**
* 请求 path 不允许出现的字符
*/
public static String[] INVALID_CHARACTER = {
"//", "\\",
"%2e", "%2E", // .
"%2f", "%2F", // /
"%5c", "%5C", // \
"%25" // 空格
};
/**
* 校验请求 path 的算法
*/
public SaCheckRequestPathFunction checkRequestPath = (requestPath, extArg1, extArg2) -> {
// 不允许为null
if(requestPath == null) {
throw new RequestPathInvalidException("非法请求null", null);
}
// 不允许包含非法字符
for (String item : INVALID_CHARACTER) {
if (requestPath.contains(item)) {
throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
}
}
// 不允许出现跨目录
if(requestPath.contains("/.") || requestPath.contains("\\.")) {
throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
}
};
/**
* 当请求 path 校验不通过时处理方案的算法自定义示例
* <pre>
* SaStrategy.instance.requestPathInvalidHandle = (e, extArg1, extArg2) -> {
* // 自定义处理逻辑 ...
* };
* </pre>
*/
public SaRequestPathInvalidHandleFunction requestPathInvalidHandle = null;
// ----------------------- 重写策略 set连缀风格

View File

@ -186,6 +186,28 @@ public class SaTokenConsts {
*/
public static final int ASSEMBLY_ORDER = -100;
/**
* 请求 path 校验过滤器的注册顺序
*/
public static final int PATH_CHECK_FILTER_ORDER = -1000;
/**
* Content-Type key
*/
public static final String CONTENT_TYPE_KEY = "Content-Type";
/**
* Content-Type text/plain; charset=utf-8
*/
public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain; charset=utf-8";
/**
* Content-Type application/json;charset=UTF-8
*/
public static final String CONTENT_TYPE_APPLICATION_JSON = "application/json;charset=UTF-8";
// =================== 废弃 ===================

View File

@ -1,10 +1,11 @@
package com.pj.test;
import cn.dev33.satoken.context.SaHolder;
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;
import cn.dev33.satoken.util.SaResult;
/**
* 测试专用Controller
* @author click33
@ -27,4 +28,13 @@ public class TestController {
return SaResult.ok();
}
// 测试 浏览器访问 http://localhost:8081/test/getRequestPath
@RequestMapping("getRequestPath")
public SaResult getRequestPath() {
System.out.println("-------------- 测试请求 path 获取");
System.out.println("request.getRequestURI() " + SpringMVCUtil.getRequest().getRequestURI());
System.out.println("saRequest.getRequestPath() " + SaHolder.getRequest().getRequestPath());
return SaResult.ok();
}
}

View File

@ -19,7 +19,7 @@ sa-token:
# 是否输出操作日志
is-log: true
spring:
spring:
data:
# redis配置
redis:

View File

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

View File

@ -23,5 +23,5 @@ public class NotFoundHandle implements ErrorController {
response.setStatus(200);
return SaResult.get(404, "not found", null);
}
}

View File

@ -1,5 +1,7 @@
package com.pj.test;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
@ -41,4 +43,13 @@ public class TestController {
return SaResult.ok();
}
// 测试 浏览器访问 http://localhost:8081/test/getRequestPath
@RequestMapping("getRequestPath")
public SaResult getRequestPath() {
System.out.println("------------ 测试访问路径获取 ");
System.out.println("SpringMVCUtil.getRequest().getRequestURI() " + SpringMVCUtil.getRequest().getRequestURI());
System.out.println("SaHolder.getRequest().getRequestPath() " + SaHolder.getRequest().getRequestPath());
return SaResult.ok();
}
}

View File

@ -19,7 +19,7 @@ sa-token:
# 是否输出操作日志
is-log: true
spring:
spring:
# redis配置
redis:
# Redis数据库索引默认为0

View File

@ -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 示例 (springboot3)
*

View File

@ -19,7 +19,7 @@ sa-token:
# 是否输出操作日志
is-log: true
spring:
spring:
# redis配置
redis:
# Redis数据库索引默认为0

View File

@ -19,7 +19,7 @@ sa-token:
# 是否输出操作日志
is-log: true
spring:
spring:
# redis配置
redis:
# Redis数据库索引默认为0

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.servlet.model;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.application.ApplicationInfo;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.servlet.error.SaServletErrorCode;
@ -124,7 +125,7 @@ public class SaRequestForServlet implements SaRequest {
*/
@Override
public String getRequestPath() {
return request.getServletPath();
return ApplicationInfo.cutPathPrefix(request.getRequestURI());
}
/**

View File

@ -0,0 +1,56 @@
/*
* 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.RequestPathInvalidException;
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;
/**
* 校验请求 path 是否合法
*
* @author click33
* @since 1.37.0
*/
@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
public class SaPathCheckFilterForReactor implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 校验本次请求 path 是否合法
try {
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == 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())));
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
}
return Mono.empty();
}
// 向下执行
return chain.filter(exchange);
}
}

View File

@ -153,8 +153,8 @@ public class SaReactorFilter implements SaFilter, WebFilter {
// 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("Content-Type") == null) {
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; 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())));

View File

@ -17,6 +17,7 @@ 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;
@ -112,7 +113,7 @@ public class SaRequestForReactor implements SaRequest {
*/
@Override
public String getRequestPath() {
return request.getURI().getPath();
return ApplicationInfo.cutPathPrefix(request.getPath().toString());
}
/**

View File

@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.reactor.spring;
import cn.dev33.satoken.reactor.filter.SaPathCheckFilterForReactor;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
@ -37,4 +38,14 @@ public class SaTokenContextRegister {
return new SaTokenContextForSpringReactor();
}
/**
* 请求 path 校验过滤器
*
* @return /
*/
@Bean
public SaPathCheckFilterForReactor saPathCheckFilterForReactor() {
return new SaPathCheckFilterForReactor();
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.RequestPathInvalidException;
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;
/**
* 校验请求 path 是否合法
*
* @author click33
* @since 1.37.0
*/
@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
public class SaPathCheckFilterForReactor implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 校验本次请求 path 是否合法
try {
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == 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())));
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
}
return Mono.empty();
}
// 向下执行
return chain.filter(exchange);
}
}

View File

@ -15,19 +15,10 @@
*/
package cn.dev33.satoken.reactor.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import cn.dev33.satoken.filter.SaFilter;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.filter.SaFilter;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
@ -35,8 +26,16 @@ import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.error.SaReactorSpringBootErrorCode;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.util.SaTokenConsts;
import 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 全局鉴权过滤器
* <p>
@ -154,8 +153,8 @@ public class SaReactorFilter implements SaFilter, WebFilter {
// 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("Content-Type") == null) {
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; 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())));

View File

@ -16,16 +16,16 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.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.ArrayList;
import java.util.List;
@ -113,7 +113,7 @@ public class SaRequestForReactor implements SaRequest {
*/
@Override
public String getRequestPath() {
return request.getURI().getPath();
return ApplicationInfo.cutPathPrefix(request.getPath().toString());
}
/**

View File

@ -15,9 +15,9 @@
*/
package cn.dev33.satoken.reactor.spring;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.reactor.filter.SaPathCheckFilterForReactor;
import org.springframework.context.annotation.Bean;
/**
* 注册 Sa-Token 所需要的 Bean
@ -37,4 +37,14 @@ public class SaTokenContextRegister {
return new SaTokenContextForSpringReactor();
}
/**
* 请求 path 校验过滤器
*
* @return /
*/
@Bean
public SaPathCheckFilterForReactor saPathCheckFilterForReactor() {
return new SaPathCheckFilterForReactor();
}
}

View File

@ -15,19 +15,19 @@
*/
package cn.dev33.satoken.servlet.model;
import java.io.IOException;
import java.util.*;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.application.ApplicationInfo;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.servlet.error.SaServletErrorCode;
import cn.dev33.satoken.util.SaFoxUtil;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.servlet.error.SaServletErrorCode;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.IOException;
import java.util.*;
/**
* SaRequest 包装类的实现Servlet
@ -41,15 +41,15 @@ public class SaRequestForServlet implements SaRequest {
* 底层Request对象
*/
protected HttpServletRequest request;
/**
* 实例化
* @param request request对象
* @param request request对象
*/
public SaRequestForServlet(HttpServletRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@ -125,7 +125,7 @@ public class SaRequestForServlet implements SaRequest {
*/
@Override
public String getRequestPath() {
return request.getServletPath();
return ApplicationInfo.cutPathPrefix(request.getRequestURI());
}
/**

View File

@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.spring;
import cn.dev33.satoken.spring.context.path.ApplicationContextPathLoading;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@ -50,5 +51,14 @@ public class SaBeanRegister {
public SaJsonTemplate getSaJsonTemplateForJackson() {
return new SaJsonTemplateForJackson();
}
/**
* 应用上下文路径加载器
* @return /
*/
@Bean
public ApplicationContextPathLoading getApplicationContextPathLoading() {
return new ApplicationContextPathLoading();
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.context.path;
import cn.dev33.satoken.application.ApplicationInfo;
import cn.dev33.satoken.util.SaFoxUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
/**
* 应用上下文路径加载器
*
* @author click33
* @since 1.37.0
*/
public class ApplicationContextPathLoading implements ApplicationRunner {
@Value("${server.servlet.context-path:}")
String contextPath;
@Value("${spring.mvc.servlet.path:}")
String servletPath;
@Override
public void run(ApplicationArguments args) throws Exception {
String routePrefix = "";
if(SaFoxUtil.isNotEmpty(contextPath)) {
if(! contextPath.startsWith("/")){
contextPath = "/" + contextPath;
}
if (contextPath.endsWith("/")) {
contextPath = contextPath.substring(0, contextPath.length() - 1);
}
routePrefix += contextPath;
}
if(SaFoxUtil.isNotEmpty(servletPath)) {
if(! servletPath.startsWith("/")){
servletPath = "/" + servletPath;
}
if (servletPath.endsWith("/")) {
servletPath = servletPath.substring(0, servletPath.length() - 1);
}
routePrefix += servletPath;
}
if(SaFoxUtil.isNotEmpty(routePrefix) && ! routePrefix.equals("/") ){
ApplicationInfo.routePrefix = routePrefix;
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.exception.RequestPathInvalidException;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaTokenConsts;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 校验请求 path 是否合法
*
* @author click33
* @since 1.37.0
*/
@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
public class SaPathCheckFilterForServlet implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 校验本次请求 path 是否合法
try {
HttpServletRequest req = (HttpServletRequest) request;
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == null) {
response.setContentType("text/plain; charset=utf-8");
response.getWriter().print(e.getMessage());
response.getWriter().flush();
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
}
return;
}
// 向下执行
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View File

@ -15,26 +15,19 @@
*/
package cn.dev33.satoken.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.core.annotation.Order;
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.util.SaTokenConsts;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Servlet 全局鉴权过滤器
@ -147,7 +140,7 @@ public class SaServletFilter implements SaFilter, Filter {
// 请注意此处默认 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("text/plain; charset=utf-8");
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
}
response.getWriter().print(result);
return;

View File

@ -15,9 +15,9 @@
*/
package cn.dev33.satoken.spring;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.filter.SaPathCheckFilterForServlet;
import org.springframework.context.annotation.Bean;
/**
* 注册 Sa-Token 框架所需要的 Bean
@ -37,4 +37,14 @@ public class SaTokenContextRegister {
return new SaTokenContextForSpring();
}
/**
* 请求 path 校验过滤器
*
* @return /
*/
@Bean
public SaPathCheckFilterForServlet saPathCheckFilterForServlet() {
return new SaPathCheckFilterForServlet();
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.exception.RequestPathInvalidException;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaTokenConsts;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.annotation.Order;
import java.io.IOException;
/**
* 校验请求 path 是否合法
*
* @author click33
* @since 1.37.0
*/
@Order(SaTokenConsts.PATH_CHECK_FILTER_ORDER)
public class SaPathCheckFilterForJakartaServlet implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 校验本次请求 path 是否合法
try {
HttpServletRequest req = (HttpServletRequest) request;
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == null) {
response.setContentType("text/plain; charset=utf-8");
response.getWriter().print(e.getMessage());
response.getWriter().flush();
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
}
return;
}
// 向下执行
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View File

@ -15,25 +15,19 @@
*/
package cn.dev33.satoken.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.Order;
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.util.SaTokenConsts;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.*;
import org.springframework.core.annotation.Order;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Servlet 全局鉴权过滤器
@ -146,7 +140,7 @@ public class SaServletFilter implements SaFilter, Filter {
// 请注意此处默认 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("text/plain; charset=utf-8");
response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
}
response.getWriter().print(result);
return;

View File

@ -15,9 +15,9 @@
*/
package cn.dev33.satoken.spring;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.filter.SaPathCheckFilterForJakartaServlet;
import org.springframework.context.annotation.Bean;
/**
* 注册 Sa-Token 框架所需要的 Bean
@ -37,4 +37,14 @@ public class SaTokenContextRegister {
return new SaTokenContextForSpringInJakartaServlet();
}
/**
* 请求 path 校验过滤器
*
* @return /
*/
@Bean
public SaPathCheckFilterForJakartaServlet saPathCheckFilterForJakartaServlet() {
return new SaPathCheckFilterForJakartaServlet();
}
}