feat: 新增 SaFirewallStrategy 防火墙策略:请求 path 黑名单校验、非法字符校验、白名单放行

This commit is contained in:
click33 2024-12-08 11:17:42 +08:00
parent 11492df031
commit 6f1094c361
7 changed files with 149 additions and 60 deletions

View File

@ -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;
}

View File

@ -16,7 +16,6 @@
package cn.dev33.satoken.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.RequestPathInvalidException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.fun.strategy.*;
import cn.dev33.satoken.session.SaSession;
@ -164,49 +163,6 @@ 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

@ -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("登录成功");
}
}

View File

@ -16,7 +16,7 @@
package cn.dev33.satoken.reactor.filter;
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 org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
@ -38,13 +38,13 @@ public class SaPathCheckFilterForReactor implements WebFilter {
// 校验本次请求 path 是否合法
try {
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
SaFirewallStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
} 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);
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
}
return Mono.empty();
}

View File

@ -16,7 +16,7 @@
package cn.dev33.satoken.reactor.filter;
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 org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
@ -38,13 +38,13 @@ public class SaPathCheckFilterForReactor implements WebFilter {
// 校验本次请求 path 是否合法
try {
SaStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
SaFirewallStrategy.instance.checkRequestPath.run(exchange.getRequest().getPath().toString(), exchange, null);
} 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);
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
} else {
SaStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, exchange, null);
}
return Mono.empty();
}

View File

@ -16,7 +16,7 @@
package cn.dev33.satoken.filter;
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 org.springframework.core.annotation.Order;
@ -39,14 +39,14 @@ public class SaPathCheckFilterForServlet implements Filter {
// 校验本次请求 path 是否合法
try {
HttpServletRequest req = (HttpServletRequest) request;
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
SaFirewallStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == null) {
if(SaFirewallStrategy.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);
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, request, response);
}
return;
}

View File

@ -16,7 +16,7 @@
package cn.dev33.satoken.filter;
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 jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
@ -39,14 +39,14 @@ public class SaPathCheckFilterForJakartaServlet implements Filter {
// 校验本次请求 path 是否合法
try {
HttpServletRequest req = (HttpServletRequest) request;
SaStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
SaFirewallStrategy.instance.checkRequestPath.run(req.getRequestURI(), request, response);
} catch (RequestPathInvalidException e) {
if(SaStrategy.instance.requestPathInvalidHandle == null) {
if(SaFirewallStrategy.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);
SaFirewallStrategy.instance.requestPathInvalidHandle.run(e, request, response);
}
return;
}