mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
feat: 新增 SaFirewallStrategy 防火墙策略:请求 path 黑名单校验、非法字符校验、白名单放行
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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.strategy;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||||
|
import cn.dev33.satoken.fun.strategy.SaCheckRequestPathFunction;
|
||||||
|
import cn.dev33.satoken.fun.strategy.SaRequestPathInvalidHandleFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sa-Token 防火墙策略
|
||||||
|
*
|
||||||
|
* @author click33
|
||||||
|
* @since 1.40.0
|
||||||
|
*/
|
||||||
|
public final class SaFirewallStrategy {
|
||||||
|
|
||||||
|
private SaFirewallStrategy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局单例引用
|
||||||
|
*/
|
||||||
|
public static final SaFirewallStrategy instance = new SaFirewallStrategy();
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------- 所有策略
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求 path 黑名单
|
||||||
|
*/
|
||||||
|
public String[] BLACK_PATHS = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求 path 白名单
|
||||||
|
*/
|
||||||
|
public String[] WHITE_PATHS = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求 path 不允许出现的字符
|
||||||
|
*/
|
||||||
|
public String[] INVALID_CHARACTER = {
|
||||||
|
"//", "\\",
|
||||||
|
"%2e", "%2E", // .
|
||||||
|
"%2f", "%2F", // /
|
||||||
|
"%5c", "%5C", // \
|
||||||
|
"%25" // 空格
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验请求 path 的算法
|
||||||
|
*/
|
||||||
|
public SaCheckRequestPathFunction checkRequestPath = (requestPath, extArg1, extArg2) -> {
|
||||||
|
// 1、如果在白名单里,则直接放行
|
||||||
|
for (String item : WHITE_PATHS) {
|
||||||
|
if (requestPath.equals(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2、如果在黑名单里,则抛出异常
|
||||||
|
for (String item : BLACK_PATHS) {
|
||||||
|
if (requestPath.equals(item)) {
|
||||||
|
throw new RequestPathInvalidException("非法请求:" + requestPath, requestPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3、检查是否包含非法字符
|
||||||
|
|
||||||
|
// 不允许为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>
|
||||||
|
* SaFirewallStrategy.instance.requestPathInvalidHandle = (e, extArg1, extArg2) -> {
|
||||||
|
* // 自定义处理逻辑 ...
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public SaRequestPathInvalidHandleFunction requestPathInvalidHandle = null;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
package cn.dev33.satoken.strategy;
|
package cn.dev33.satoken.strategy;
|
||||||
|
|
||||||
import cn.dev33.satoken.SaManager;
|
import cn.dev33.satoken.SaManager;
|
||||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
|
||||||
import cn.dev33.satoken.exception.SaTokenException;
|
import cn.dev33.satoken.exception.SaTokenException;
|
||||||
import cn.dev33.satoken.fun.strategy.*;
|
import cn.dev33.satoken.fun.strategy.*;
|
||||||
import cn.dev33.satoken.session.SaSession;
|
import cn.dev33.satoken.session.SaSession;
|
||||||
@@ -164,49 +163,6 @@ public final class SaStrategy {
|
|||||||
return new StpLogic(loginType);
|
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连缀风格
|
// ----------------------- 重写策略 set连缀风格
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.pj.test;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试专用Controller
|
||||||
|
* @author click33
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class Test2Controller {
|
||||||
|
|
||||||
|
// 测试登录 ---- http://localhost:8081/.test
|
||||||
|
@RequestMapping("/.test")
|
||||||
|
public SaResult test2() {
|
||||||
|
System.out.println("--- 进来了");
|
||||||
|
return SaResult.ok("登录成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+4
-4
@@ -16,7 +16,7 @@
|
|||||||
package cn.dev33.satoken.reactor.filter;
|
package cn.dev33.satoken.reactor.filter;
|
||||||
|
|
||||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||||
import cn.dev33.satoken.strategy.SaStrategy;
|
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||||
import cn.dev33.satoken.util.SaTokenConsts;
|
import cn.dev33.satoken.util.SaTokenConsts;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
@@ -38,13 +38,13 @@ public class SaPathCheckFilterForReactor implements WebFilter {
|
|||||||
|
|
||||||
// 校验本次请求 path 是否合法
|
// 校验本次请求 path 是否合法
|
||||||
try {
|
try {
|
||||||
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
|
SaFirewallStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
|
||||||
} catch (RequestPathInvalidException e) {
|
} catch (RequestPathInvalidException e) {
|
||||||
if(SaStrategy.instance.requestPathInvalidHandle == null) {
|
if(SaFirewallStrategy.instance.requestPathInvalidHandle == null) {
|
||||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
||||||
} else {
|
} else {
|
||||||
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
|
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -16,7 +16,7 @@
|
|||||||
package cn.dev33.satoken.reactor.filter;
|
package cn.dev33.satoken.reactor.filter;
|
||||||
|
|
||||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||||
import cn.dev33.satoken.strategy.SaStrategy;
|
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||||
import cn.dev33.satoken.util.SaTokenConsts;
|
import cn.dev33.satoken.util.SaTokenConsts;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
@@ -38,13 +38,13 @@ public class SaPathCheckFilterForReactor implements WebFilter {
|
|||||||
|
|
||||||
// 校验本次请求 path 是否合法
|
// 校验本次请求 path 是否合法
|
||||||
try {
|
try {
|
||||||
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
|
SaFirewallStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
|
||||||
} catch (RequestPathInvalidException e) {
|
} catch (RequestPathInvalidException e) {
|
||||||
if(SaStrategy.instance.requestPathInvalidHandle == null) {
|
if(SaFirewallStrategy.instance.requestPathInvalidHandle == null) {
|
||||||
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
exchange.getResponse().getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);
|
||||||
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
|
||||||
} else {
|
} else {
|
||||||
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
|
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -16,7 +16,7 @@
|
|||||||
package cn.dev33.satoken.filter;
|
package cn.dev33.satoken.filter;
|
||||||
|
|
||||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||||
import cn.dev33.satoken.strategy.SaStrategy;
|
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||||
import cn.dev33.satoken.util.SaTokenConsts;
|
import cn.dev33.satoken.util.SaTokenConsts;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
@@ -39,14 +39,14 @@ public class SaPathCheckFilterForServlet implements Filter {
|
|||||||
// 校验本次请求 path 是否合法
|
// 校验本次请求 path 是否合法
|
||||||
try {
|
try {
|
||||||
HttpServletRequest req = (HttpServletRequest) request;
|
HttpServletRequest req = (HttpServletRequest) request;
|
||||||
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
|
SaFirewallStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
|
||||||
} catch (RequestPathInvalidException e) {
|
} catch (RequestPathInvalidException e) {
|
||||||
if(SaStrategy.instance.requestPathInvalidHandle == null) {
|
if(SaFirewallStrategy.instance.requestPathInvalidHandle == null) {
|
||||||
response.setContentType("text/plain; charset=utf-8");
|
response.setContentType("text/plain; charset=utf-8");
|
||||||
response.getWriter().print(e.getMessage());
|
response.getWriter().print(e.getMessage());
|
||||||
response.getWriter().flush();
|
response.getWriter().flush();
|
||||||
} else {
|
} else {
|
||||||
SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
|
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, request, response);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -16,7 +16,7 @@
|
|||||||
package cn.dev33.satoken.filter;
|
package cn.dev33.satoken.filter;
|
||||||
|
|
||||||
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
import cn.dev33.satoken.exception.RequestPathInvalidException;
|
||||||
import cn.dev33.satoken.strategy.SaStrategy;
|
import cn.dev33.satoken.strategy.SaFirewallStrategy;
|
||||||
import cn.dev33.satoken.util.SaTokenConsts;
|
import cn.dev33.satoken.util.SaTokenConsts;
|
||||||
import jakarta.servlet.*;
|
import jakarta.servlet.*;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -39,14 +39,14 @@ public class SaPathCheckFilterForJakartaServlet implements Filter {
|
|||||||
// 校验本次请求 path 是否合法
|
// 校验本次请求 path 是否合法
|
||||||
try {
|
try {
|
||||||
HttpServletRequest req = (HttpServletRequest) request;
|
HttpServletRequest req = (HttpServletRequest) request;
|
||||||
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
|
SaFirewallStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
|
||||||
} catch (RequestPathInvalidException e) {
|
} catch (RequestPathInvalidException e) {
|
||||||
if(SaStrategy.instance.requestPathInvalidHandle == null) {
|
if(SaFirewallStrategy.instance.requestPathInvalidHandle == null) {
|
||||||
response.setContentType("text/plain; charset=utf-8");
|
response.setContentType("text/plain; charset=utf-8");
|
||||||
response.getWriter().print(e.getMessage());
|
response.getWriter().print(e.getMessage());
|
||||||
response.getWriter().flush();
|
response.getWriter().flush();
|
||||||
} else {
|
} else {
|
||||||
SaStrategy.instance.requestPathInvalidHandle.run(e, request, response);
|
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, request, response);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user