refactor: 删除heritage-common异常和过滤器
- 移除所有异常处理类 - 移除XSS过滤器相关组件 - 移除拦截器组件 - 清理安全相关基础设施
This commit is contained in:
parent
7b7b767a5a
commit
45517ee01f
@ -1,30 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.exception;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.leocoder.heritage.common.enmus.common.IResultEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @version 1.0
|
|
||||||
* @date 2025-06-28 14:09
|
|
||||||
* @description [业务异常]
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public class BusinessException extends RuntimeException {
|
|
||||||
|
|
||||||
private Integer status = 500;
|
|
||||||
private String message = "业务异常,请联系管理员";
|
|
||||||
|
|
||||||
public BusinessException(int status, String message) {
|
|
||||||
super(message);
|
|
||||||
this.status = status;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BusinessException(IResultEnum resultEnum) {
|
|
||||||
super(resultEnum.message());
|
|
||||||
this.status = resultEnum.status();
|
|
||||||
this.message = resultEnum.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.exception;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.leocoder.heritage.common.enmus.common.IResultEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @version 1.0
|
|
||||||
* @date 2025-06-28 14:09
|
|
||||||
* @description [RateLimitException]
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public class RateLimiterException extends RuntimeException {
|
|
||||||
|
|
||||||
private int status;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public RateLimiterException(int status, String message) {
|
|
||||||
super(message);
|
|
||||||
this.status = status;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RateLimiterException(IResultEnum resultEnum) {
|
|
||||||
super(resultEnum.message());
|
|
||||||
this.status = resultEnum.status();
|
|
||||||
this.message = resultEnum.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.exception;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.leocoder.heritage.common.enmus.common.IResultEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @version 1.0
|
|
||||||
* @date 2025-06-28 14:09
|
|
||||||
* @description [RepeatSubmitException]
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public class RepeatSubmitException extends RuntimeException {
|
|
||||||
private int status;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public RepeatSubmitException(int status, String message) {
|
|
||||||
super(message);
|
|
||||||
this.status = status;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RepeatSubmitException(IResultEnum resultEnum) {
|
|
||||||
super(resultEnum.message());
|
|
||||||
this.status = resultEnum.status();
|
|
||||||
this.message = resultEnum.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.exception.coder;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.leocoder.heritage.common.enmus.common.IResultEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @version 1.0
|
|
||||||
* @date 2025-06-28 14:09
|
|
||||||
* @description [新增定义参数异常}
|
|
||||||
* RuntimeException 运行时异常,只要报错即不会向下运行
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public class ParamsException extends RuntimeException {
|
|
||||||
private Integer status = 500;
|
|
||||||
private String message = "自定义异常,请联系管理员";
|
|
||||||
|
|
||||||
public ParamsException() {
|
|
||||||
super("自定义异常,请联系管理员");
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParamsException(String message) {
|
|
||||||
super(message);
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParamsException(Integer status) {
|
|
||||||
super("自定义异常,请联系管理员");
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParamsException(Integer status, String message) {
|
|
||||||
super(message);
|
|
||||||
this.status = status;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 枚举类,不用可以删除
|
|
||||||
public ParamsException(IResultEnum resultEnum) {
|
|
||||||
super(resultEnum.message());
|
|
||||||
this.status = resultEnum.status();
|
|
||||||
this.message = resultEnum.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.exception.coder;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.leocoder.heritage.common.enmus.common.IResultEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @version 1.0
|
|
||||||
* @date 2025-06-28 14:09
|
|
||||||
* 用来代替if()else{}的判断
|
|
||||||
* flag == true的时候,抛出参数异常
|
|
||||||
*/
|
|
||||||
public class YUtil {
|
|
||||||
|
|
||||||
/* 注意:使用 YUtil 工具类,StringUtils.isBlank()、StringUtils.isNotBlank(),flag为true则抛异常 */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [普通直接抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isTrue(String message) {
|
|
||||||
throw new ParamsException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [枚举直接抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isTrue(IResultEnum resultEnum) {
|
|
||||||
throw new ParamsException(resultEnum);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param flag 条件为true,抛出异常
|
|
||||||
* @param message 异常信息
|
|
||||||
* @description [普通抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isTrue(Boolean flag, String message) {
|
|
||||||
if (flag) {
|
|
||||||
throw new ParamsException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param flag 条件为true,抛出异常
|
|
||||||
* @param resultEnum 异常信息
|
|
||||||
* @description [枚举抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isTrue(Boolean flag, IResultEnum resultEnum) {
|
|
||||||
if (flag) {
|
|
||||||
throw new ParamsException(resultEnum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [实体类抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isNull(Object data, String message) {
|
|
||||||
if (ObjectUtils.isEmpty(data)) {
|
|
||||||
throw new ParamsException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [实体类枚举抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isNull(Object data, IResultEnum resultEnum) {
|
|
||||||
if (ObjectUtils.isEmpty(data)) {
|
|
||||||
throw new ParamsException(resultEnum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [String枚举抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isNull(String data, String message) {
|
|
||||||
if (StringUtils.isBlank(data)) {
|
|
||||||
throw new ParamsException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [String枚举抛出异常]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
public static void isNull(String data, IResultEnum resultEnum) {
|
|
||||||
if (StringUtils.isBlank(data)) {
|
|
||||||
throw new ParamsException(resultEnum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.filter;
|
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import jakarta.servlet.ReadListener;
|
|
||||||
import jakarta.servlet.ServletInputStream;
|
|
||||||
import jakarta.servlet.ServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @description [构建可重复读取inputStream的request]
|
|
||||||
*/
|
|
||||||
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
|
|
||||||
private final byte[] body;
|
|
||||||
|
|
||||||
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
|
|
||||||
super(request);
|
|
||||||
request.setCharacterEncoding("UTF-8");
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
|
|
||||||
body = IoUtil.readBytes(request.getInputStream(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BufferedReader getReader() throws IOException {
|
|
||||||
return new BufferedReader(new InputStreamReader(getInputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
|
||||||
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
|
|
||||||
return new ServletInputStream() {
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return bais.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
return body.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReadListener(ReadListener readListener) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.filter;
|
|
||||||
|
|
||||||
import jakarta.servlet.*;
|
|
||||||
import jakarta.servlet.annotation.WebFilter;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @description [XssFilter]
|
|
||||||
*/
|
|
||||||
// xss:跨站脚本攻击,通过前端input将js脚本注入到后台
|
|
||||||
// sql注入:将恶意的sql命令注入到后台数据库引擎执行
|
|
||||||
// csrf:跨站请求伪造,以用户身份在攻击页面对目标网站发起伪造用户操作的请求
|
|
||||||
// 其中xss和sql注入可以通过拦截请求并进行特殊字符过滤来防御,而csrf需要进行referer检测和token校验进行防御,
|
|
||||||
// 如果只做了referer检测,在实际的第三方机构检测中,csrf漏洞还是存在的,所以需要进行token校验,token校验在这里不做过多说明,只进行referer检测。
|
|
||||||
@Slf4j
|
|
||||||
@WebFilter
|
|
||||||
public class XssFilter implements Filter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(FilterConfig filterConfig) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response,
|
|
||||||
FilterChain chain) throws IOException, ServletException {
|
|
||||||
request.setCharacterEncoding("utf-8");
|
|
||||||
// 升级SpringBoot 3.4.1后,影响验证码无法正常显示。
|
|
||||||
// response.setContentType("text/html;charset=utf-8");
|
|
||||||
/**
|
|
||||||
* 跨域设置
|
|
||||||
* 跨域:由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的 数据接口;
|
|
||||||
* 前后端都需要设置允许跨域;
|
|
||||||
*/
|
|
||||||
// if (response instanceof HttpServletResponse httpServletResponse) {
|
|
||||||
// // 通过在响应 header 中设置 ‘*’ 来允许来自所有域的跨域请求访问。
|
|
||||||
// httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
|
|
||||||
// // 通过对 Credentials 参数的设置,就可以保持跨域 Ajax 时的 Cookie
|
|
||||||
// // 设置了Allow-Credentials,Allow-Origin就不能为*,需要指明具体的url域
|
|
||||||
// // httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
|
|
||||||
// // 请求方式
|
|
||||||
// httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
|
|
||||||
// // [预检请求]的返回结果[即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息]可以被缓存多久
|
|
||||||
// httpServletResponse.setHeader("Access-Control-Max-Age", "86400");
|
|
||||||
// // 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段
|
|
||||||
// httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
|
|
||||||
// }
|
|
||||||
// sql,xss过滤
|
|
||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
|
||||||
// log.info("XssFilter-original url:{},ParameterMap:{}", httpServletRequest.getRequestURI(), JSONObject.toJSONString(httpServletRequest.getParameterMap()));
|
|
||||||
XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(httpServletRequest);
|
|
||||||
chain.doFilter(xssHttpServletRequestWrapper, response);
|
|
||||||
// log.info("XssFilter-doFilter url:{},ParameterMap:{}", xssHttpServletRequestWrapper.getRequestURI(), JSONObject.toJSONString(xssHttpServletRequestWrapper.getParameterMap()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.filter;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @description [XssHttpServletRequestWrapper]
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|
||||||
|
|
||||||
private static final Set<String> notAllowedKeyWords = new HashSet<String>(0);
|
|
||||||
|
|
||||||
static {
|
|
||||||
String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+";
|
|
||||||
String[] keyStr = key.split("\\|");
|
|
||||||
notAllowedKeyWords.addAll(Arrays.asList(keyStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String currentUrl;
|
|
||||||
|
|
||||||
public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
|
|
||||||
super(servletRequest);
|
|
||||||
currentUrl = servletRequest.getRequestURI();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。
|
|
||||||
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
|
|
||||||
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getParameter(String parameter) {
|
|
||||||
String value = super.getParameter(parameter);
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return cleanXSS(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getParameterValues(String parameter) {
|
|
||||||
String[] values = super.getParameterValues(parameter);
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int count = values.length;
|
|
||||||
String[] encodedValues = new String[count];
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
encodedValues[i] = cleanXSS(values[i]);
|
|
||||||
}
|
|
||||||
return encodedValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String[]> getParameterMap() {
|
|
||||||
Map<String, String[]> values = super.getParameterMap();
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, String[]> result = new HashMap<>();
|
|
||||||
for (String key : values.keySet()) {
|
|
||||||
String encodedKey = cleanXSS(key);
|
|
||||||
int count = values.get(key).length;
|
|
||||||
String[] encodedValues = new String[count];
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
encodedValues[i] = cleanXSS(values.get(key)[i]);
|
|
||||||
}
|
|
||||||
result.put(encodedKey, encodedValues);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。
|
|
||||||
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取
|
|
||||||
* getHeaderNames 也可能需要覆盖
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getHeader(String name) {
|
|
||||||
String value = super.getHeader(name);
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return cleanXSS(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String cleanXSS(String valueP) {
|
|
||||||
// You'll need to remove the spaces from the html entities below
|
|
||||||
String value = valueP.replaceAll("<", "<").replaceAll(">", ">");
|
|
||||||
value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
|
|
||||||
value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
|
|
||||||
value = value.replaceAll("'", "& #39;");
|
|
||||||
value = value.replaceAll("eval\\((.*)\\)", "");
|
|
||||||
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
|
|
||||||
value = value.replaceAll("script", "");
|
|
||||||
value = cleanSqlKeyWords(value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String cleanSqlKeyWords(String value) {
|
|
||||||
String paramValue = value;
|
|
||||||
for (String keyword : notAllowedKeyWords) {
|
|
||||||
if (paramValue.length() > keyword.length() + 4
|
|
||||||
&& (paramValue.contains(" " + keyword) || paramValue.contains(keyword + " ") || paramValue.contains(" " + keyword + " "))) {
|
|
||||||
String replacedString = "INVALID";
|
|
||||||
paramValue = StringUtils.replace(paramValue, keyword, replacedString);
|
|
||||||
log.warn(this.currentUrl + "已被过滤,因为参数中包含不允许sql的关键词[" + keyword
|
|
||||||
+ "]" + "-[参数]:" + value + "-[过滤后的参数]:" + paramValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paramValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,162 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.interceptor;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.leocoder.heritage.common.constants.CoderCacheConstants;
|
|
||||||
import org.leocoder.heritage.common.constants.CoderConstants;
|
|
||||||
import org.leocoder.heritage.common.exception.coder.ParamsException;
|
|
||||||
import org.leocoder.heritage.common.utils.cache.RedisLimitUtil;
|
|
||||||
import org.leocoder.heritage.common.utils.cache.RedisUtil;
|
|
||||||
import org.leocoder.heritage.common.utils.ip.IpUtil;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @description [黑白名单IP拦截]
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class CoderIpBlockListInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
@Value("${Coder.globalLimit.enabled:false}")
|
|
||||||
private Boolean globalLimitEnabled;
|
|
||||||
|
|
||||||
@Value("${Coder.globalLimit.time:1}")
|
|
||||||
private Integer globalLimitTime;
|
|
||||||
|
|
||||||
@Value("${Coder.globalLimit.count:12}")
|
|
||||||
private Integer globalLimitCount;
|
|
||||||
|
|
||||||
@Value("${Coder.globalLimit.ban:false}")
|
|
||||||
private Boolean globalLimitBan;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RedisUtil redisUtil;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RedisLimitUtil redisLimitUtil;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(@Nullable HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Object handler) {
|
|
||||||
if (request != null) {
|
|
||||||
ipMatch(IpUtil.getIpAddr(request));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description [IP匹配]
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
private void ipMatch(String currentIp) {
|
|
||||||
// 校验IP有效性
|
|
||||||
if (StringUtils.isBlank(currentIp)) {
|
|
||||||
throw new ParamsException("IP地址获取失败,禁止该用户访问");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取真实IP[处理多IP情况]
|
|
||||||
currentIp = Arrays.stream(currentIp.split(","))
|
|
||||||
.findFirst()
|
|
||||||
.map(String::trim)
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("无效IP,禁止访问"));
|
|
||||||
|
|
||||||
// 白名单Redis缓存数据
|
|
||||||
List<String> whiteIpKeyList = redisUtil.getKeysByPrefix(CoderCacheConstants.WHITELIST_IP_KEY);
|
|
||||||
boolean isWhite = false;
|
|
||||||
|
|
||||||
if (CollectionUtil.isNotEmpty(whiteIpKeyList)) {
|
|
||||||
for (String whiteIpKey : whiteIpKeyList) {
|
|
||||||
String whiteIp = redisUtil.getKey(whiteIpKey);
|
|
||||||
if (StringUtils.isNotEmpty(whiteIp)) {
|
|
||||||
String regex = whiteIp.replace("?", ".").replace("*", ".*");
|
|
||||||
Pattern compiledPattern = Pattern.compile(regex);
|
|
||||||
Matcher matcher = compiledPattern.matcher(currentIp);
|
|
||||||
|
|
||||||
if (matcher.matches()) {
|
|
||||||
isWhite = true;
|
|
||||||
// log.info("白名单IP[{}],请求已通过白名单检查", currentIp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 没有白名单Redis数据,不进行校验
|
|
||||||
} else {
|
|
||||||
isWhite = CoderConstants.TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isWhite) {
|
|
||||||
log.error("非白名单IP[{}],请求已拒绝", currentIp);
|
|
||||||
throw new ParamsException("暂时无法为您提供服务[WHITE]!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 黑名单Redis缓存数据
|
|
||||||
boolean blackHasKey = redisUtil.hasKey(CoderCacheConstants.BLACKLIST_IP_KEY + currentIp);
|
|
||||||
if (blackHasKey) {
|
|
||||||
String blackIp = redisUtil.getKey(CoderCacheConstants.BLACKLIST_IP_KEY + currentIp);
|
|
||||||
if (StringUtils.isNotEmpty(blackIp)) {
|
|
||||||
String regex = blackIp.replace("?", ".").replace("*", ".*");
|
|
||||||
Pattern compiledPattern = Pattern.compile(regex);
|
|
||||||
Matcher matcher = compiledPattern.matcher(currentIp);
|
|
||||||
|
|
||||||
if (matcher.matches()) {
|
|
||||||
log.error("黑名单IP[{}],请求已拒绝", currentIp);
|
|
||||||
throw new ParamsException("暂时无法为您提供服务[BLACK]!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.info("IP[{}],通过白名单和黑名单检查,请求已通过", currentIp);
|
|
||||||
|
|
||||||
// 全局限流,yml根据业务进行配置
|
|
||||||
if (globalLimitEnabled) {
|
|
||||||
checkRateLimit(currentIp);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkRateLimit(String currentIp) {
|
|
||||||
String blackKey = CoderCacheConstants.BLACKLIST_IP_KEY + currentIp;
|
|
||||||
String limitKey = CoderCacheConstants.RATE_LIMIT_KEY + currentIp;
|
|
||||||
// 全局黑名单模式,访问频繁直接拉黑
|
|
||||||
if (globalLimitBan) {
|
|
||||||
boolean blackHasKey = redisUtil.hasKey(blackKey);
|
|
||||||
if (blackHasKey) {
|
|
||||||
// 判断是否超出限流次数
|
|
||||||
if (!redisLimitUtil.limit(limitKey, globalLimitCount, globalLimitTime)) {
|
|
||||||
log.info("全局黑名单IP[EXIST]:{}", currentIp);
|
|
||||||
String blackIp = redisUtil.getKey(blackKey);
|
|
||||||
if (StringUtils.isNotEmpty(blackIp)) {
|
|
||||||
redisUtil.setCacheObject(blackKey, currentIp);
|
|
||||||
}
|
|
||||||
throw new ParamsException("暂时无法为您提供服务[GLOBAL]");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 判断是否超出限流次数
|
|
||||||
if (!redisLimitUtil.limit(limitKey, globalLimitCount, globalLimitTime)) {
|
|
||||||
log.info("全局黑名单IP[NULL]:{}", currentIp);
|
|
||||||
redisUtil.setCacheObject(blackKey, currentIp);
|
|
||||||
throw new ParamsException("暂时无法为您提供服务[GLOBAL]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 全局限流模式,访问频繁进行限流
|
|
||||||
} else {
|
|
||||||
// 判断是否超出限流次数
|
|
||||||
if (!redisLimitUtil.limit(limitKey, globalLimitCount, globalLimitTime)) {
|
|
||||||
log.info("全局限流IP:{}", currentIp);
|
|
||||||
throw new ParamsException("访问过于频繁,请稍候再试[GLOBAL]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.interceptor;
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.UUID;
|
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.MDC;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author : Leocoder
|
|
||||||
* @description [CoderTrackIdInterceptor]
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class CoderTrackIdInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Object handler) {
|
|
||||||
String traceId = String.format("%s - %s", request.getRequestURI(), UUID.fastUUID().toString(true));
|
|
||||||
// 可以考虑让客户端传入链路ID,但需保证一定的复杂度唯一性;如果没使用默认UUID自动生成
|
|
||||||
if (StringUtils.isNotBlank(request.getHeader("TRACE_ID"))){
|
|
||||||
traceId = request.getHeader("TRACE_ID");
|
|
||||||
}
|
|
||||||
MDC.put("traceId", traceId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
package org.leocoder.heritage.common.interceptor;
|
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.dreamlu.mica.core.utils.StringUtil;
|
|
||||||
import org.apache.commons.lang3.time.StopWatch;
|
|
||||||
import org.leocoder.heritage.common.filter.RepeatedlyRequestWrapper;
|
|
||||||
import org.leocoder.heritage.common.utils.json.JsonUtil;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* web的调用时间统计拦截器
|
|
||||||
* dev环境有效
|
|
||||||
*
|
|
||||||
* @author : Leocoder
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class CoderWebInvokeTimeInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
private final String prodProfile = "prod";
|
|
||||||
|
|
||||||
private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
||||||
if (!prodProfile.equals(SpringUtil.getActiveProfile())) {
|
|
||||||
String url = request.getMethod() + " " + request.getRequestURI();
|
|
||||||
|
|
||||||
// 打印请求参数
|
|
||||||
if (isJsonRequest(request)) {
|
|
||||||
String jsonParam = "";
|
|
||||||
if (request instanceof RepeatedlyRequestWrapper) {
|
|
||||||
BufferedReader reader = request.getReader();
|
|
||||||
jsonParam = IoUtil.read(reader);
|
|
||||||
}
|
|
||||||
log.info("[Coder]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
|
||||||
} else {
|
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
|
||||||
if (MapUtil.isNotEmpty(parameterMap)) {
|
|
||||||
String parameters = JsonUtil.toJsonString(parameterMap);
|
|
||||||
log.info("[Coder]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
|
||||||
} else {
|
|
||||||
log.info("[Coder]开始请求 => URL[{}],无参数", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StopWatch stopWatch = new StopWatch();
|
|
||||||
KEY_CACHE.set(stopWatch);
|
|
||||||
stopWatch.start();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
|
||||||
if (!prodProfile.equals(SpringUtil.getActiveProfile())) {
|
|
||||||
StopWatch stopWatch = KEY_CACHE.get();
|
|
||||||
stopWatch.stop();
|
|
||||||
log.info("[Coder]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime(TimeUnit.MILLISECONDS));
|
|
||||||
KEY_CACHE.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断本次请求的数据类型是否为json
|
|
||||||
*
|
|
||||||
* @param request request
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
private boolean isJsonRequest(HttpServletRequest request) {
|
|
||||||
String contentType = request.getContentType();
|
|
||||||
if (contentType != null) {
|
|
||||||
return StringUtil.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user