springboot 实现xss过滤
warning:
这篇文章距离上次修改已过456天,其中的内容可能已经有所变动。
package com.jinw.cms.aspectj;
import com.jinw.cms.aspectj.annotation.XssIgnore;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
public class XssInterceptor implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
XssIgnore xssIgnore = handlerMethod.getMethodAnnotation(XssIgnore.class);
if (Objects.nonNull(xssIgnore)) {
XssContextHolder.ignore();
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
XssContextHolder.remove();
}
/**
* 如果返回一个current类型的变量,会启用一个新的线程。执行完preHandle方法之后立即会调用afterConcurrentHandlingStarted
* 然后新线程再以次执行preHandle,postHandle,afterCompletion**
*/
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
XssContextHolder.remove();
}
}
package com.jinw.cms.aspectj;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.jinw.cms.entity.XssMode;
import com.jinw.utils.cms.HtmlUtils;
import com.jinw.utils.cms.StringUtils;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.Objects;
@RequiredArgsConstructor
public class XssDeserializer extends JsonDeserializer<String> {
private final XssMode mode;
@Override
public LogicalType logicalType() {
return LogicalType.Textual;
}
@Override
public String getNullValue(DeserializationContext ctxt) throws JsonMappingException {
return StringUtils.EMPTY;
}
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
if (p.hasToken(JsonToken.VALUE_STRING)) {
return deal(p.getText());
}
JsonToken token = p.getCurrentToken();
if (token.isScalarValue()) {
String text = p.getValueAsString();
if (text != null) {
return text;
}
}
return (String) ctxt.handleUnexpectedToken(String.class, p);
}
private String deal(String text) {
if (Objects.nonNull(text) && !XssContextHolder.isIgnore()) {
if (mode == XssMode.CLEAN) {
text = HtmlUtils.clean(text);
} else {
text = HtmlUtils.escape(text);
}
}
return text;
}
}
package com.jinw.cms.aspectj;
import java.util.Objects;
public class XssContextHolder {
private static final ThreadLocal<Boolean> CONTEXT = new ThreadLocal<>();
/**
* 默认为true
*/
public static boolean isIgnore() {
return Objects.isNull(CONTEXT.get()) ? true : CONTEXT.get();
}
public static void ignore() {
CONTEXT.set(true);
}
public static void remove() {
CONTEXT.remove();
}
}
package com.jinw.cms.config.properties;
import com.jinw.cms.entity.XssMode;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Setter
@Getter
@ConfigurationProperties(prefix = "xss")
public class XssProperties {
/**
* 是否开启XSS过滤
*/
private boolean enabled;
/**
* 处理方式
*/
private XssMode mode;
/**
* 不进行处理的路径
*/
private List<String> excludes;
/**
* 处理指定路径
*/
private List<String> urlPatterns;
}
package com.jinw.cms.config;
import com.google.common.collect.Lists;
import com.jinw.cms.aspectj.XssDeserializer;
import com.jinw.cms.aspectj.XssInterceptor;
import com.jinw.cms.config.properties.XssProperties;
import com.jinw.utils.cms.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Collections;
import java.util.List;
@RequiredArgsConstructor
@Configuration
@EnableConfigurationProperties(XssProperties.class)
public class XssConfig implements WebMvcConfigurer {
private final XssProperties xssProperties;
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (xssProperties.isEnabled()) {
List<String> urlPatterns = xssProperties.getUrlPatterns();
if (StringUtils.isEmpty(urlPatterns)) {
urlPatterns = Lists.newArrayList();
urlPatterns.add("/**");
}
List<String> excludes = xssProperties.getExcludes();
if (StringUtils.isEmpty(excludes)) {
excludes = Collections.emptyList();
}
//addPathPatterns(urlPatterns) 对所有请求都拦截,但是排除了 excludePathPatterns(excludes) 请求的拦截。
registry.addInterceptor(new XssInterceptor()).
addPathPatterns(urlPatterns)
.excludePathPatterns(excludes).order(Ordered.LOWEST_PRECEDENCE);
}
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer xssCustomizer() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.deserializerByType(String.class,
new XssDeserializer(xssProperties.getMode()));
}
}
package com.jinw.cms.aspectj.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface XssIgnore {
}
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
mode: clean