系统配置
This commit is contained in:
parent
15b6bef566
commit
a7ed6ae5ab
|
|
@ -19,7 +19,7 @@ server:
|
||||||
port: 8080
|
port: 8080
|
||||||
servlet:
|
servlet:
|
||||||
# 应用的访问路径
|
# 应用的访问路径
|
||||||
context-path: /
|
context-path: /smartArchives
|
||||||
tomcat:
|
tomcat:
|
||||||
# tomcat的URI编码
|
# tomcat的URI编码
|
||||||
uri-encoding: UTF-8
|
uri-encoding: UTF-8
|
||||||
|
|
@ -34,7 +34,7 @@ server:
|
||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.bonus: debug
|
com.bonus: info
|
||||||
org.springframework: warn
|
org.springframework: warn
|
||||||
|
|
||||||
# 用户配置
|
# 用户配置
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public class RequestCoverFilter implements Filter {
|
||||||
|
|
||||||
// 处理不同类型的请求
|
// 处理不同类型的请求
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
log.info("请求头中无Content-Type信息,处理URL参数。");
|
// log.info("请求头中无Content-Type信息,处理URL参数。");
|
||||||
handleUrlParams(httpRequest, response, chain, integrality, encrypt);
|
handleUrlParams(httpRequest, response, chain, integrality, encrypt);
|
||||||
} else if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
|
} else if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
|
||||||
handleBodyRequest(httpRequest, response, chain, integrality, encrypt);
|
handleBodyRequest(httpRequest, response, chain, integrality, encrypt);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.bonus.common.utils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全验证的工具类
|
||||||
|
* 提供用于检测SQL注入、特殊字符、脚本注入等的工具方法
|
||||||
|
* 作者: GeYazhong
|
||||||
|
* 日期: 2021/11/23 10:54
|
||||||
|
*/
|
||||||
|
public class SafeUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全SQL模式,用于检测SQL注入的正则表达式
|
||||||
|
* 包含常见的SQL注入关键字和注释符号
|
||||||
|
*/
|
||||||
|
public final static String SAFE_SQL_PATTERN = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|"
|
||||||
|
+ "(\\b(select|update|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全脚本模式,用于检测脚本注入的正则表达式
|
||||||
|
* 由于平台中setfilter中使用多个参数时用到&符号,因此未包含&符号
|
||||||
|
*/
|
||||||
|
public final static String SAFE_SCRIPT_PATTERN =
|
||||||
|
"(\\||;|\\$|'|\\'|0x0d|0x0a|\\%27|\\%3B" +
|
||||||
|
"|<>|\\[\\]|\\(\\)|/|\"" +
|
||||||
|
"|script|alert|svg|confirm|prompt|onload" +
|
||||||
|
"|%3c|%3e|%2b|@|!|img|src" +
|
||||||
|
"|%|_)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查特殊字符的正则表达式
|
||||||
|
* 用于判断字符串是否为数字、字母、下划线或汉字
|
||||||
|
*/
|
||||||
|
public static final String CHECK_SPECIAL = "^[\u4E00-\u9FA5A-Za-z_][\u4E00-\u9FA5A-Za-z0-9_]{0,}$";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证字符串是否包含SQL注入关键字
|
||||||
|
*
|
||||||
|
* @param mark 需要检查的字符串
|
||||||
|
* @return 如果包含SQL注入关键字返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean checkSafeSql(String mark) {
|
||||||
|
if (mark != null && !"".equals(mark)) {
|
||||||
|
return match(SAFE_SQL_PATTERN, mark.toLowerCase().trim());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证字符串是否包含特殊字符
|
||||||
|
*
|
||||||
|
* @param mark 需要检查的字符串
|
||||||
|
* @return 如果包含特殊字符返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean checkSpecial(String mark) {
|
||||||
|
if (mark != null && !"".equals(mark)) {
|
||||||
|
return match(SAFE_SQL_PATTERN, mark.toLowerCase().trim());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证字符串是否包含特殊脚本字符
|
||||||
|
*
|
||||||
|
* @param mark 需要检查的字符串
|
||||||
|
* @return 如果包含特殊脚本字符返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean checkScript(String mark) {
|
||||||
|
if (mark != null && !"".equals(mark)) {
|
||||||
|
return match(SAFE_SCRIPT_PATTERN, mark.toLowerCase().trim());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行正则表达式匹配
|
||||||
|
*
|
||||||
|
* @param pattern 正则表达式模式
|
||||||
|
* @param str 需要验证的字符串
|
||||||
|
* @return 如果匹配返回true,否则返回false
|
||||||
|
*/
|
||||||
|
private static boolean match(String pattern, String str) {
|
||||||
|
Pattern p = Pattern.compile(pattern);
|
||||||
|
Matcher m = p.matcher(str);
|
||||||
|
return m.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否符合正则表达式
|
||||||
|
*
|
||||||
|
* @param regex 正则表达式
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果匹配返回true,否则返回false
|
||||||
|
*/
|
||||||
|
private static boolean isMatch(String regex, String orginal) {
|
||||||
|
if (StringUtils.isEmpty(orginal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher isNum = pattern.matcher(orginal);
|
||||||
|
return isNum.matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为正整数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是正整数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isPositiveInteger(String orginal) {
|
||||||
|
return isMatch("^\\+{0,1}[1-9]\\d*", orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为负整数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是负整数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isNegativeInteger(String orginal) {
|
||||||
|
return isMatch("^-[1-9]\\d*", orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为整数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是整数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isWholeNumber(String orginal) {
|
||||||
|
return isMatch("[+-]{0,1}0", orginal) || isPositiveInteger(orginal) || isNegativeInteger(orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为正小数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是正小数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isPositiveDecimal(String orginal) {
|
||||||
|
return isMatch("\\+{0,1}[0]\\.[1-9]*|\\+{0,1}[1-9]\\d*\\.\\d*", orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为负小数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是负小数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isNegativeDecimal(String orginal) {
|
||||||
|
return isMatch("^-[0]\\.[1-9]*|^-[1-9]\\d*\\.\\d*", orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为小数
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是小数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isDecimal(String orginal) {
|
||||||
|
return isMatch("[-+]{0,1}\\d+\\.\\d*|[-+]{0,1}\\d*\\.\\d+", orginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为实数(整数或小数)
|
||||||
|
*
|
||||||
|
* @param orginal 需要检查的字符串
|
||||||
|
* @return 如果是实数返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public static boolean isRealNumber(String orginal) {
|
||||||
|
return isWholeNumber(orginal) || isDecimal(orginal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.bonus.framework.config;
|
package com.bonus.framework.config;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.bonus.framework.interceptor.ParamSecureHandler;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
@ -26,6 +28,12 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||||
@Autowired
|
@Autowired
|
||||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ParamSecureHandler paramSecureHandler;
|
||||||
|
|
||||||
|
/** 不需要拦截地址 "/logout",*/
|
||||||
|
public static final String[] EXCLUDEURLS = { "/login", "/refresh" };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
||||||
{
|
{
|
||||||
|
|
@ -46,6 +54,11 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||||
public void addInterceptors(InterceptorRegistry registry)
|
public void addInterceptors(InterceptorRegistry registry)
|
||||||
{
|
{
|
||||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
||||||
|
//自定义拦截器
|
||||||
|
registry.addInterceptor(paramSecureHandler)
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns(EXCLUDEURLS)
|
||||||
|
.order(-10);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,11 @@ import org.springframework.stereotype.Component;
|
||||||
public class ReplayAttackFilter implements Filter {
|
public class ReplayAttackFilter implements Filter {
|
||||||
static List<String> ignoreUrlPatterns = new ArrayList<>();
|
static List<String> ignoreUrlPatterns = new ArrayList<>();
|
||||||
static {
|
static {
|
||||||
ignoreUrlPatterns.add("/captchaImage");
|
ignoreUrlPatterns.add("/smartArchives/captchaImage");
|
||||||
ignoreUrlPatterns.add("/login");
|
ignoreUrlPatterns.add("/smartArchives/login");
|
||||||
ignoreUrlPatterns.add("/loginOut");
|
ignoreUrlPatterns.add("/smartArchives/logout");
|
||||||
ignoreUrlPatterns.add("/getInfo");
|
ignoreUrlPatterns.add("/smartArchives/getInfo");
|
||||||
ignoreUrlPatterns.add("/getRouters");
|
ignoreUrlPatterns.add("/smartArchives/getRouters");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -69,7 +69,6 @@ public class ReplayAttackFilter implements Filter {
|
||||||
boolean shouldIgnore = ignoreUrlPatterns.contains(requestURI);
|
boolean shouldIgnore = ignoreUrlPatterns.contains(requestURI);
|
||||||
if (shouldIgnore) {
|
if (shouldIgnore) {
|
||||||
// 忽略该请求的处理
|
// 忽略该请求的处理
|
||||||
System.out.println("忽略请求: " + requestURI);
|
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +171,6 @@ public class ReplayAttackFilter implements Filter {
|
||||||
|
|
||||||
// 构建待签名字符串
|
// 构建待签名字符串
|
||||||
String signString = buildSignString(encryptUserId, timestamp, requestUrl, request.getMethod());
|
String signString = buildSignString(encryptUserId, timestamp, requestUrl, request.getMethod());
|
||||||
System.err.println(signString);
|
|
||||||
// 使用HMAC-SHA256计算签名
|
// 使用HMAC-SHA256计算签名
|
||||||
String calculatedSignature = calculateHMAC(signString, encryptSecret);
|
String calculatedSignature = calculateHMAC(signString, encryptSecret);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
package com.bonus.framework.interceptor;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.bonus.common.core.domain.AjaxResult;
|
||||||
|
import com.bonus.common.utils.SafeUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.AsyncHandlerInterceptor;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bonus
|
||||||
|
* @data 2023/2/6 17:22
|
||||||
|
* @description 安全参数验证
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
||||||
|
|
||||||
|
static List<String> ignoreUrlPatterns = new ArrayList<>();
|
||||||
|
static {
|
||||||
|
ignoreUrlPatterns.add("/smartArchives/captchaImage");
|
||||||
|
ignoreUrlPatterns.add("/smartArchives/login");
|
||||||
|
ignoreUrlPatterns.add("/smartArchives/logout");
|
||||||
|
ignoreUrlPatterns.add("/smartArchives/getInfo");
|
||||||
|
ignoreUrlPatterns.add("/smartArchives/getRouters");
|
||||||
|
}
|
||||||
|
private String rnd = null;
|
||||||
|
|
||||||
|
public static String ur = "/";
|
||||||
|
|
||||||
|
public static int max_length = 4;
|
||||||
|
|
||||||
|
private static Map<String, List<Double>> requestLogMap = null;
|
||||||
|
|
||||||
|
public boolean isFileUpload(HttpServletRequest request) {
|
||||||
|
String contentType = request.getContentType();
|
||||||
|
if (StringUtils.isEmpty(contentType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 过滤部分请求
|
||||||
|
String requestUrl = request.getRequestURI();
|
||||||
|
boolean shouldIgnore = ignoreUrlPatterns.contains(requestUrl);
|
||||||
|
if (shouldIgnore) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 过滤文件上传功能
|
||||||
|
if (isFileUpload(request)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验参数是否合法
|
||||||
|
*/
|
||||||
|
if (!requestWrapper.isChecked()) {
|
||||||
|
log.error("输入值非法: queryString={}, body={}",
|
||||||
|
StringUtils.defaultString(requestWrapper.getQueryString(), "null"),
|
||||||
|
StringUtils.defaultString(requestWrapper.getReaderParam(), "null"));
|
||||||
|
returnJson(response, "输入值非法", 500);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// System.err.println(JSON.toJSONString(request.getParameterMap()));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有跳转路径参数,保留传入下个界面
|
||||||
|
*/
|
||||||
|
Map<String, String[]> map = requestWrapper.getParameterMap();
|
||||||
|
boolean checkParameterMap = checkParameterMap(map, requestUrl);
|
||||||
|
if (!checkParameterMap) {
|
||||||
|
returnJson(response, "输入值非法", 500);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据流参数
|
||||||
|
*/
|
||||||
|
String readerParam = requestWrapper.getReaderParam();
|
||||||
|
// 判断是否是文件上传,是不对流参数进行验证
|
||||||
|
String uplFile = "/upload", upImage = "/uploadFiles",path="/";
|
||||||
|
if (!requestUrl.contains(uplFile) && !requestUrl.contains(upImage) && !requestUrl.contains(path)) {
|
||||||
|
boolean checkReader = checkReader(readerParam, requestUrl);
|
||||||
|
if (!checkReader) {
|
||||||
|
returnJson(response, "不安全参数", 500);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void returnJson(HttpServletResponse response, String msg, int code) {
|
||||||
|
response.reset();
|
||||||
|
PrintWriter writer = null;
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("applicatiopn/json;charset=utf-8");
|
||||||
|
AjaxResult a = AjaxResult.error(code, msg);
|
||||||
|
String res = JSON.toJSONString(a);
|
||||||
|
try {
|
||||||
|
writer = response.getWriter();
|
||||||
|
writer.println(res);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查所有页面输入的参数是否安全 request.getParameterMap()
|
||||||
|
*
|
||||||
|
* @param map
|
||||||
|
* @param requestUrl
|
||||||
|
*/
|
||||||
|
private boolean checkParameterMap(Map<String, String[]> map, String requestUrl) {
|
||||||
|
rnd = null;
|
||||||
|
if (map != null && map.size() > 0) {
|
||||||
|
Iterator<Map.Entry<String, String[]>> iterator = map.entrySet().iterator();
|
||||||
|
String value = "";
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
value = "";
|
||||||
|
Map.Entry<String, String[]> entry = iterator.next();
|
||||||
|
String pname = (String) entry.getKey();
|
||||||
|
if (Objects.equals("token", pname)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object objValue = entry.getValue();
|
||||||
|
if (null == objValue) {
|
||||||
|
value = "";
|
||||||
|
} else {
|
||||||
|
String[] values = (String[]) objValue;
|
||||||
|
for (String s : values) {
|
||||||
|
value = s + ",";
|
||||||
|
}
|
||||||
|
if (value.length() > 0) {
|
||||||
|
value = value.substring(0, value.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("rnd".equals(pname)) {
|
||||||
|
rnd = value;
|
||||||
|
}
|
||||||
|
if (SafeUtil.checkSafeSql(value)) {
|
||||||
|
log.info("请求失败,当前请求参数不安全!请求地址:\n" + requestUrl + "\n不安全参数:" + pname + ":" + value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (SafeUtil.checkSpecial(value) || SafeUtil.checkScript(value)) {
|
||||||
|
log.info("请求失败,当前请求参数包含特殊字符!请求地址:\n" + requestUrl + "\n特殊字符参数:" + pname + ":" + value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过rnd随机参数判断相同url是否多次请求访问
|
||||||
|
*
|
||||||
|
* @param currentRequest
|
||||||
|
* @param requestWrapper
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean checkSameUrl(String currentRequest, XssRequestWrapper requestWrapper) {
|
||||||
|
if (rnd != null && rnd.length() > 0) {
|
||||||
|
try {
|
||||||
|
double newRnd = Double.parseDouble(rnd);
|
||||||
|
/**
|
||||||
|
* 当为空时,新增请求时间记录,第一次请求,放行,允许查询
|
||||||
|
*/
|
||||||
|
HttpSession session = requestWrapper.getSession();
|
||||||
|
Object obj = session.getAttribute("requestLogMap");
|
||||||
|
if (obj == null) {
|
||||||
|
requestLogMap = new HashMap<String, List<Double>>(100);
|
||||||
|
} else {
|
||||||
|
requestLogMap = (Map<String, List<Double>>) obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Double> list = requestLogMap.get(currentRequest);
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<Double>();
|
||||||
|
} else {
|
||||||
|
for (Double oldRnd : list) {
|
||||||
|
if (oldRnd == newRnd) {
|
||||||
|
log.info("请求失败,当前请求已过期!请重新登录!您的请求地址:\n" + currentRequest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.add(newRnd);
|
||||||
|
requestLogMap.put(currentRequest, list);
|
||||||
|
String brute = "requestLogMap";
|
||||||
|
if (session.getAttribute(brute) != null) {
|
||||||
|
session.removeAttribute(brute);
|
||||||
|
}
|
||||||
|
session.setAttribute(brute, requestLogMap);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.info("rnd参数格式化错误 请检查");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("请求失败,rnd参数不存在!请重新登录!您的请求地址:\n" + currentRequest);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查所有页面输入的参数是否安全 request.getReader()
|
||||||
|
*
|
||||||
|
* @param readerParam
|
||||||
|
* @param requestUrl
|
||||||
|
*/
|
||||||
|
private boolean checkReader(String readerParam, String requestUrl) {
|
||||||
|
if (SafeUtil.checkScript(readerParam) || SafeUtil.checkSafeSql(readerParam)) {
|
||||||
|
log.info("请求失败,当前请求参数不安全!请求地址:\n" + requestUrl + "\n不安全参数:数据流:" + readerParam);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
|
||||||
|
throws Exception {
|
||||||
|
// SecurityContextHolder.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,35 +25,42 @@ import java.util.regex.Pattern;
|
||||||
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
private byte[] body;
|
private byte[] body;
|
||||||
|
|
||||||
private String queryString;
|
private String queryString;
|
||||||
|
|
||||||
private String streamParam;
|
private String streamParam;
|
||||||
|
|
||||||
private boolean checked;
|
private boolean checked;
|
||||||
|
|
||||||
public XssRequestWrapper(HttpServletRequest request) {
|
public XssRequestWrapper(HttpServletRequest request) {
|
||||||
super(request);
|
super(request);
|
||||||
getParameterMap();
|
|
||||||
BufferedReader reader;
|
// 先读取请求体数据
|
||||||
try {
|
try {
|
||||||
reader = request.getReader();
|
// 读取请求体内容
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
char[] buf = new char[1024];
|
BufferedReader reader = request.getReader();
|
||||||
int rd;
|
char[] charBuffer = new char[1024];
|
||||||
while ((rd = reader.read(buf)) != -1) {
|
int bytesRead;
|
||||||
sb.append(buf, 0, rd);
|
while ((bytesRead = reader.read(charBuffer)) > 0) {
|
||||||
|
stringBuilder.append(charBuffer, 0, bytesRead);
|
||||||
}
|
}
|
||||||
reader.close();
|
String requestBody = stringBuilder.toString();
|
||||||
streamParam = xssClean(sb.toString());
|
|
||||||
setChecked(xssCleanNew(sb.toString()) && xssCleanNew(request.getQueryString()));
|
// 进行XSS清理
|
||||||
body = streamParam.getBytes();
|
streamParam = xssClean(requestBody);
|
||||||
|
body = streamParam.getBytes(request.getCharacterEncoding() != null ?
|
||||||
|
request.getCharacterEncoding() : "UTF-8");
|
||||||
|
|
||||||
|
// 检查安全性
|
||||||
|
String queryStr = request.getQueryString();
|
||||||
|
setChecked(xssCleanNew(requestBody) && (queryStr == null || xssCleanNew(queryStr)));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.getLocalizedMessage(),e);
|
log.error("读取请求体数据失败: {}", e.getLocalizedMessage(), e);
|
||||||
|
body = new byte[0];
|
||||||
|
streamParam = "";
|
||||||
|
setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryString = xssClean(request.getQueryString());
|
queryString = xssClean(request.getQueryString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -63,135 +70,132 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedReader getReader() throws IOException {
|
public BufferedReader getReader() throws IOException {
|
||||||
return new BufferedReader(new InputStreamReader(getInputStream()));
|
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
public ServletInputStream getInputStream() throws IOException {
|
||||||
|
|
||||||
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
|
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
|
||||||
return new ServletInputStream() {
|
return new ServletInputStream() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isFinished() {
|
public boolean isFinished() {
|
||||||
return false;
|
return bais.available() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReadListener(ReadListener listener) {
|
public void setReadListener(ReadListener listener) {
|
||||||
|
// 不需要实现
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
int read = bais.read();
|
return bais.read();
|
||||||
bais.close();
|
|
||||||
return read;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String[]> getParameterMap() {
|
public Map<String, String[]> getParameterMap() {
|
||||||
Map<String, String[]> requestMap = super.getParameterMap();
|
Map<String, String[]> requestMap = super.getParameterMap();
|
||||||
if(requestMap!=null){
|
if (requestMap != null) {
|
||||||
Iterator iterator = requestMap.entrySet().iterator();
|
Iterator<Map.Entry<String, String[]>> iterator = requestMap.entrySet().iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Map.Entry me = (Map.Entry) iterator.next();
|
Map.Entry<String, String[]> entry = iterator.next();
|
||||||
String[] values = (String[]) me.getValue();
|
String[] values = entry.getValue();
|
||||||
for (int i = 0; i < values.length; i++) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
if (values[i] != null) {
|
if (values[i] != null) {
|
||||||
values[i] = xssClean(values[i]);
|
values[i] = xssClean(values[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
}
|
||||||
return requestMap;
|
return requestMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getParameterValues(String paramString) {
|
public String[] getParameterValues(String paramString) {
|
||||||
String[] arrayOfString1 = super.getParameterValues(paramString);
|
String[] values = super.getParameterValues(paramString);
|
||||||
if (arrayOfString1 == null){
|
if (values == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int i = arrayOfString1.length;
|
|
||||||
String[] arrayOfString2 = new String[i];
|
|
||||||
|
|
||||||
for (int j = 0; j < i; j++) {
|
String[] cleanedValues = new String[values.length];
|
||||||
if (arrayOfString1[j] != null) {
|
for (int i = 0; i < values.length; i++) {
|
||||||
arrayOfString2[j] = xssClean(arrayOfString1[j]);
|
if (values[i] != null) {
|
||||||
|
cleanedValues[i] = xssClean(values[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arrayOfString2;
|
return cleanedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getParameter(String paramString) {
|
public String getParameter(String paramString) {
|
||||||
String str = super.getParameter(paramString);
|
String value = super.getParameter(paramString);
|
||||||
if (str == null){
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return xssClean(str);
|
return xssClean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHeader(String paramString) {
|
public String getHeader(String paramString) {
|
||||||
String str = super.getHeader(paramString);
|
String value = super.getHeader(paramString);
|
||||||
if (str == null){
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return xssClean(str);
|
return xssClean(value);
|
||||||
}
|
}
|
||||||
String regex1="<script>(.*?)</script>";
|
|
||||||
String regex2="src[\r\n]*=[\r\n]*'(.*?)'";
|
// 正则表达式模式
|
||||||
String regex3="src[\r\n]*=[\r\n]*\"(.*?)\"";
|
private static final String regex1 = "<script>(.*?)</script>";
|
||||||
String regex4="</script>";
|
private static final String regex2 = "src[\r\n]*=[\r\n]*'(.*?)'";
|
||||||
String regex5="<script(.*?)>";
|
private static final String regex3 = "src[\r\n]*=[\r\n]*\"(.*?)\"";
|
||||||
String regex6="eval\\((.*?)\\)";
|
private static final String regex4 = "</script>";
|
||||||
String regex7="e-xpression\\((.*?)\\)";
|
private static final String regex5 = "<script(.*?)>";
|
||||||
String regex8="javascript:";
|
private static final String regex6 = "eval\\((.*?)\\)";
|
||||||
String regex9="vbscript:";
|
private static final String regex7 = "e-xpression\\((.*?)\\)";
|
||||||
String regex10="onload(.*?)=";
|
private static final String regex8 = "javascript:";
|
||||||
|
private static final String regex9 = "vbscript:";
|
||||||
|
private static final String regex10 = "onload(.*?)=";
|
||||||
|
|
||||||
|
// 添加安全脚本模式
|
||||||
|
public static final String SAFE_SCRIPT_PATTERN = "(\\||;|\\$|'|\\'|0x0d|0x0a|\\%27|\\%3B|<>|\\[\\]|\\(\\)|/|\"|script|alert|svg|confirm|prompt|onload|%3c|%3e|%2b|@|!|img|src)";
|
||||||
|
|
||||||
private String xssClean(String value) {
|
private String xssClean(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return value;
|
return null;
|
||||||
}
|
|
||||||
value = value.replaceAll("", "");
|
|
||||||
|
|
||||||
Pattern scriptPattern = Pattern.compile(regex1, Pattern.CASE_INSENSITIVE);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex4, Pattern.CASE_INSENSITIVE);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex5, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex6, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex7, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex8, Pattern.CASE_INSENSITIVE);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex9, Pattern.CASE_INSENSITIVE);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
scriptPattern = Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
|
||||||
value = scriptPattern.matcher(value).replaceAll("");
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移除空字符串替换(这行代码没有实际效果)
|
||||||
|
// value = value.replaceAll("", "");
|
||||||
|
|
||||||
|
// 使用预编译的模式提高性能
|
||||||
|
value = Pattern.compile(regex1, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex4, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex5, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex6, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex7, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex8, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex9, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
|
||||||
|
value = Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL).matcher(value).replaceAll("");
|
||||||
|
// 添加额外的安全过滤
|
||||||
|
value = Pattern.compile(SAFE_SCRIPT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(value).replaceAll("");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean xssCleanNew(String value) {
|
private boolean xssCleanNew(String value) {
|
||||||
boolean find = false;
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
List<Pattern> patterns = new ArrayList<Pattern>();
|
|
||||||
|
List<Pattern> patterns = new ArrayList<>();
|
||||||
patterns.add(Pattern.compile(regex1, Pattern.CASE_INSENSITIVE));
|
patterns.add(Pattern.compile(regex1, Pattern.CASE_INSENSITIVE));
|
||||||
patterns.add(Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
patterns.add(Pattern.compile(regex2, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
patterns.add(Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
patterns.add(Pattern.compile(regex3, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
|
@ -202,29 +206,21 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
patterns.add(Pattern.compile(regex8, Pattern.CASE_INSENSITIVE));
|
patterns.add(Pattern.compile(regex8, Pattern.CASE_INSENSITIVE));
|
||||||
patterns.add(Pattern.compile(regex9, Pattern.CASE_INSENSITIVE));
|
patterns.add(Pattern.compile(regex9, Pattern.CASE_INSENSITIVE));
|
||||||
patterns.add(Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
patterns.add(Pattern.compile(regex10, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL));
|
||||||
|
patterns.add(Pattern.compile(SAFE_SCRIPT_PATTERN, Pattern.CASE_INSENSITIVE));
|
||||||
|
|
||||||
for (Pattern pattern : patterns) {
|
for (Pattern pattern : patterns) {
|
||||||
find = match(pattern, value) ;
|
if (match(pattern, value)) {
|
||||||
if (find) {
|
return false; // 发现XSS攻击
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return !find;
|
return true; // 安全
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行正则表达式
|
* 执行正则表达式匹配
|
||||||
*
|
|
||||||
* @param pattern
|
|
||||||
* 表达式
|
|
||||||
* @param str
|
|
||||||
* 待验证字符串
|
|
||||||
* @return 返回 <b>true </b>,否则为 <b>false </b>
|
|
||||||
*/
|
*/
|
||||||
private static boolean match(Pattern pattern, String str) {
|
private static boolean match(Pattern pattern, String str) {
|
||||||
Matcher m = pattern.matcher(str);
|
return pattern.matcher(str).find();
|
||||||
return m.find();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReaderParam() {
|
public String getReaderParam() {
|
||||||
|
|
@ -238,4 +234,11 @@ public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
public void setChecked(boolean checked) {
|
public void setChecked(boolean checked) {
|
||||||
this.checked = checked;
|
this.checked = checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 添加字符编码支持
|
||||||
|
public String getCharacterEncoding() {
|
||||||
|
String encoding = super.getCharacterEncoding();
|
||||||
|
return encoding != null ? encoding : "UTF-8";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue