feat:V3.1.1整合remi-comm到remi-cloud中
This commit is contained in:
67
remi-comm/remi-comm-safe/pom.xml
Normal file
67
remi-comm/remi-comm-safe/pom.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>remi-comm-safe</artifactId>
|
||||
<name>remi-comm-safe</name>
|
||||
<description>remi-comm-safe</description>
|
||||
|
||||
<parent>
|
||||
<groupId>ai.remi.top.cloud</groupId>
|
||||
<artifactId>remi-comm</artifactId>
|
||||
<version>3.1.1</version>
|
||||
</parent>
|
||||
|
||||
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>maven-releases</id>
|
||||
<name>release</name>
|
||||
<url>https://repository.njydsz.com/repository/maven-releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>maven-snapshots</id>
|
||||
<name>snapshot</name>
|
||||
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<!--公共依赖-->
|
||||
<dependency>
|
||||
<groupId>ai.remi.top.cloud</groupId>
|
||||
<artifactId>remi-comm-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.remi.top.cloud</groupId>
|
||||
<artifactId>remi-comm-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.remi.top.cloud</groupId>
|
||||
<artifactId>remi-comm-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.remi.top.cloud</groupId>
|
||||
<artifactId>remi-comm-exception</artifactId>
|
||||
</dependency>
|
||||
<!--spring-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<!--lombok-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,47 @@
|
||||
package ai.remi.comm.safe.annotation;
|
||||
|
||||
import ai.remi.comm.safe.constant.SafeConstants;
|
||||
import ai.remi.comm.safe.enums.LimitType;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 基于Redis的防刷限流注解
|
||||
*
|
||||
* @author DianJiu 【公众号 点九开源】
|
||||
* @email dianjiuxyz@gmail.com
|
||||
* @date 2022-07-19
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public @interface RedisLimit {
|
||||
/**
|
||||
* key
|
||||
*/
|
||||
String key() default SafeConstants.REDIS_LIMIT_KEY;
|
||||
|
||||
/**
|
||||
* Key的前缀
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* 一定时间内最多访问次数
|
||||
*/
|
||||
int count();
|
||||
|
||||
/**
|
||||
* 给定的时间范围 单位(秒)
|
||||
*/
|
||||
int period();
|
||||
|
||||
/**
|
||||
* 限流的类型(用户自定义key或者请求ip)
|
||||
*/
|
||||
LimitType limitType() default LimitType.CUSTOMER;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ai.remi.comm.safe.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 自定义注解防止表单重复提交
|
||||
*
|
||||
* @author DianJiu 【公众号 点九开源】
|
||||
* @email dianjiuxyz@gmail.com
|
||||
* @date 2022-07-19
|
||||
*/
|
||||
@Inherited
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RepeatSubmit {
|
||||
/**
|
||||
* 间隔时间(ms),小于此时间视为重复提交
|
||||
*/
|
||||
public int interval() default 5000;
|
||||
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
public String message() default "不允许重复提交,请稍候再试";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ai.remi.comm.safe.annotation;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 基于Sentinel的防刷限流注解
|
||||
*
|
||||
* @author DianJiu 【公众号 点九开源】
|
||||
* @email dianjiuxyz@gmail.com
|
||||
* @date 2022-07-19
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public @interface SentinelLimit {
|
||||
|
||||
String resourceName();
|
||||
|
||||
int limitCount() default 5;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ai.remi.comm.safe.annotation;
|
||||
|
||||
import ai.remi.comm.safe.aspect.XssValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
|
||||
@Constraint(validatedBy = {XssValidator.class})
|
||||
public @interface Xss {
|
||||
String message() default "不允许任何脚本运行";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package ai.remi.comm.safe.aspect;
|
||||
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import ai.remi.comm.exception.custom.BusinessException;
|
||||
import ai.remi.comm.safe.annotation.RedisLimit;
|
||||
import ai.remi.comm.safe.enums.LimitType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(10)
|
||||
public class RedisLimitAspect {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedisLimitAspect.class);
|
||||
private final RedisTemplate<String, Serializable> limitRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private DefaultRedisScript<Number> redisLuaScript;
|
||||
|
||||
@Autowired
|
||||
public RedisLimitAspect(RedisTemplate<String, Serializable> limitRedisTemplate) {
|
||||
this.limitRedisTemplate = limitRedisTemplate;
|
||||
}
|
||||
|
||||
|
||||
@Pointcut(value = "@annotation(ai.remi.comm.safe.annotation.RedisLimit)")
|
||||
public void redisLimit() {
|
||||
|
||||
}
|
||||
|
||||
@Around("redisLimit()")
|
||||
public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
// 获取连接点所在类名
|
||||
String classname = joinPoint.getTarget().getClass().getSimpleName();
|
||||
// 获取连接点所在方法名称
|
||||
String methodName = method.getName();
|
||||
String functionName = String.format("【%s.%s()】", classname, methodName);
|
||||
|
||||
RedisLimit redisLimit = method.getAnnotation(RedisLimit.class);
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
|
||||
LimitType limitType = redisLimit.limitType();
|
||||
String key = redisLimit.key();
|
||||
if (StringUtils.isEmpty(key)) {
|
||||
if (limitType == LimitType.IP) {
|
||||
key = getIpAddr(request);
|
||||
} else {
|
||||
key = redisLimit.key();
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableList<String> keys = ImmutableList.of(StringUtils.join(redisLimit.prefix(), key));
|
||||
|
||||
//执行到这里开始走进来的方法体(必须声明)
|
||||
Object returnValue;
|
||||
try {
|
||||
String luaScript = buildLuaScript();
|
||||
redisLuaScript = new DefaultRedisScript<>(luaScript, Number.class);
|
||||
//调用lua脚本,获取返回结果,这里即为请求的次数
|
||||
Number number = limitRedisTemplate.execute(
|
||||
redisLuaScript,
|
||||
keys,
|
||||
redisLimit.count(),
|
||||
redisLimit.period()
|
||||
);
|
||||
if (number == null || number.intValue() == 0) {
|
||||
throw new BusinessException("111111", "访问频率过快,被限流了");
|
||||
}
|
||||
logger.info("第{}次访问 [{}] 接口!", number, functionName);
|
||||
returnValue = joinPoint.proceed();
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
throw new BusinessException(((BusinessException) throwable).getCode(), (throwable).getMessage());
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限流脚本
|
||||
*/
|
||||
private String buildLuaScript() {
|
||||
return "local limit = tonumber(ARGV[1])" +
|
||||
"local current = tonumber(redis.call('get', key) or '0')" +
|
||||
"if current + 1 > limit then" +
|
||||
" return 0" +
|
||||
"else" +
|
||||
" current = redis.call('incr',KEYS[1])" +
|
||||
" redis.call('expire',KEYS[1],ARGV[2])" +
|
||||
" return current" +
|
||||
"end;";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的IP方法
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private static String getIpAddr(HttpServletRequest request) {
|
||||
String ipAddress = null;
|
||||
try {
|
||||
ipAddress = request.getHeader("x-forwarded-for");
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
}
|
||||
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
||||
if (ipAddress != null && ipAddress.length() > 15) {
|
||||
if (ipAddress.indexOf(",") > 0) {
|
||||
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ipAddress = "";
|
||||
}
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package ai.remi.comm.safe.aspect;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.Tracer;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
|
||||
import ai.remi.comm.safe.annotation.SentinelLimit;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(10)
|
||||
public class SentinelLimitAspect {
|
||||
private static void initFlowRule(String resourceName,int limitCount) {
|
||||
List<FlowRule> rules = new ArrayList<>();
|
||||
FlowRule rule = new FlowRule();
|
||||
//设置受保护的资源
|
||||
rule.setResource(resourceName);
|
||||
//设置流控规则 QPS
|
||||
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
//设置受保护的资源阈值
|
||||
rule.setCount(limitCount);
|
||||
rules.add(rule);
|
||||
//加载配置好的规则
|
||||
FlowRuleManager.loadRules(rules);
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(ai.remi.comm.safe.annotation.SentinelLimit)")
|
||||
public void rateLimit() {
|
||||
|
||||
}
|
||||
|
||||
@Around("rateLimit()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) {
|
||||
//1、获取当前的调用方法
|
||||
Method currentMethod = getCurrentMethod(joinPoint);
|
||||
if (Objects.isNull(currentMethod)) {
|
||||
return null;
|
||||
}
|
||||
//2、从方法注解定义上获取限流的类型
|
||||
String resourceName = currentMethod.getAnnotation(SentinelLimit.class).resourceName();
|
||||
if(StringUtils.isEmpty(resourceName)){
|
||||
throw new RuntimeException("资源名称为空");
|
||||
}
|
||||
int limitCount = currentMethod.getAnnotation(SentinelLimit.class).limitCount();
|
||||
initFlowRule(resourceName,limitCount);
|
||||
|
||||
Entry entry = null;
|
||||
Object result = null;
|
||||
try {
|
||||
entry = SphU.entry(resourceName);
|
||||
try {
|
||||
result = joinPoint.proceed();
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
} catch (BlockException ex) {
|
||||
// 资源访问阻止,被限流或被降级
|
||||
// 在此处进行相应的处理操作
|
||||
System.out.println("blocked");
|
||||
return "被限流了";
|
||||
} catch (Exception e) {
|
||||
Tracer.traceEntry(e, entry);
|
||||
} finally {
|
||||
if (entry != null) {
|
||||
entry.exit();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Method getCurrentMethod(JoinPoint joinPoint) {
|
||||
Method[] methods = joinPoint.getTarget().getClass().getMethods();
|
||||
Method target = null;
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(joinPoint.getSignature().getName())) {
|
||||
target = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ai.remi.comm.safe.aspect;
|
||||
|
||||
import ai.remi.comm.safe.annotation.Xss;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class XssValidator implements ConstraintValidator<Xss, String> {
|
||||
private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return true;
|
||||
}
|
||||
return !containsHtml(value);
|
||||
}
|
||||
|
||||
public static boolean containsHtml(String value) {
|
||||
Pattern pattern = Pattern.compile(HTML_PATTERN);
|
||||
Matcher matcher = pattern.matcher(value);
|
||||
return matcher.matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ai.remi.comm.safe.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Configuration
|
||||
public class RedisLimiterHelper {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory factory) {
|
||||
RedisTemplate<String, Serializable> template = new RedisTemplate<String, Serializable>();
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
template.setConnectionFactory(factory);
|
||||
return template;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ai.remi.comm.safe.constant;
|
||||
|
||||
/**
|
||||
* 缓存的key 常量
|
||||
*/
|
||||
public class SafeConstants {
|
||||
|
||||
|
||||
/**
|
||||
* 防重提交 redis key
|
||||
*/
|
||||
public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
|
||||
|
||||
/**
|
||||
* 限流 redis key
|
||||
*/
|
||||
public static final String REDIS_LIMIT_KEY = "redis_limit:";
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ai.remi.comm.safe.enums;
|
||||
|
||||
/**
|
||||
* 限制的类型
|
||||
*/
|
||||
public enum LimitType {
|
||||
|
||||
/**
|
||||
* 自定义key
|
||||
*/
|
||||
CUSTOMER,
|
||||
/**
|
||||
* 根据请求者IP
|
||||
*/
|
||||
IP;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package ai.remi.comm.safe.filter;
|
||||
|
||||
import ai.remi.comm.core.enums.HttpMethod;
|
||||
import ai.remi.comm.util.string.StringUtils;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 防止XSS攻击的过滤器
|
||||
*
|
||||
*/
|
||||
public class XssFilter implements Filter {
|
||||
/**
|
||||
* 排除链接
|
||||
*/
|
||||
public List<String> excludes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
String tempExcludes = filterConfig.getInitParameter("excludes");
|
||||
if (StringUtils.isNotEmpty(tempExcludes)) {
|
||||
String[] url = tempExcludes.split(",");
|
||||
for (int i = 0; url != null && i < url.length; i++) {
|
||||
excludes.add(url[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
if (handleExcludeURL(req, resp)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
|
||||
chain.doFilter(xssRequest, response);
|
||||
}
|
||||
|
||||
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
|
||||
String url = request.getServletPath();
|
||||
String method = request.getMethod();
|
||||
// GET DELETE 不过滤
|
||||
if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) {
|
||||
return true;
|
||||
}
|
||||
return StringUtils.matches(url, excludes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package ai.remi.comm.safe.filter;
|
||||
|
||||
import ai.remi.comm.util.html.EscapeUtils;
|
||||
import ai.remi.comm.util.string.StringUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* XSS过滤处理
|
||||
*/
|
||||
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
/**
|
||||
* @param request
|
||||
*/
|
||||
public XssHttpServletRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
String[] values = super.getParameterValues(name);
|
||||
if (values != null) {
|
||||
int length = values.length;
|
||||
String[] escapesValues = new String[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
// 防xss攻击和过滤前后空格
|
||||
escapesValues[i] = EscapeUtils.clean(values[i]).trim();
|
||||
}
|
||||
return escapesValues;
|
||||
}
|
||||
return super.getParameterValues(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
// 非json类型,直接返回
|
||||
if (!isJsonRequest()) {
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
// 为空,直接返回
|
||||
String json = IOUtils.toString(super.getInputStream(), "utf-8");
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return super.getInputStream();
|
||||
}
|
||||
|
||||
// xss过滤
|
||||
json = EscapeUtils.clean(json).trim();
|
||||
byte[] jsonBytes = json.getBytes("utf-8");
|
||||
final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return jsonBytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return bis.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是Json请求
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
public boolean isJsonRequest() {
|
||||
String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
|
||||
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user