1. XSS攻击的本质与SpringBoot防护必要性
在Web应用开发中,跨站脚本攻击(XSS)长期位列OWASP十大安全威胁前三甲。攻击者通过在网页中注入恶意脚本,当其他用户浏览时,这些脚本会在其浏览器端执行,轻则窃取用户会话cookie,重则盗取敏感数据甚至控制用户账户。去年某电商平台就因未正确处理商品评论中的脚本标签,导致数百万用户信息泄露。
SpringBoot作为Java生态中最流行的Web框架,其自动配置特性虽然提升了开发效率,但也可能让开发者忽略底层安全细节。我曾审计过一个SpringBoot项目,发现开发者直接使用@ResponseBody返回未过滤的用户输入,导致存储型XSS漏洞。这促使我系统研究SpringBoot下的XSS防护方案。
2. 请求入口层的防御策略
2.1 过滤器(Filter)方案实现
在Servlet层面拦截是最彻底的防护方式。以下是基于Jsoup的XSS过滤过滤器实现:
java复制public class XssFilter implements Filter {
private static final AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (isExcludePath(httpRequest.getRequestURI())) {
chain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper wrappedRequest =
new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
private boolean isExcludePath(String uri) {
List<String> excludePaths = Arrays.asList("/api/public/**", "/static/**");
return excludePaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, uri));
}
}
关键点在于XssHttpServletRequestWrapper中对请求参数的净化处理:
java复制public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return StringUtils.isEmpty(value) ? value : Jsoup.clean(value, Safelist.basic());
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) return null;
return Arrays.stream(values)
.map(v -> Jsoup.clean(v, Safelist.basic()))
.toArray(String[]::new);
}
}
警告:直接使用Jsoup的
basic()安全列表会过滤掉所有HTML标签,包括<b>,<i>等合法标签。如果业务需要富文本,应自定义Safelist:java复制Safelist.relaxed() .addTags("div","span") .addAttributes("span", "style");
2.2 拦截器(Interceptor)的辅助校验
对于RESTful接口,可以在拦截器中做二次校验:
java复制public class XssInterceptor implements HandlerInterceptor {
private static final Pattern[] XSS_PATTERNS = {
Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE)
};
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
checkParameters(request.getParameterMap());
}
return true;
}
private void checkParameters(Map<String, String[]> paramMap) {
paramMap.forEach((key, values) -> {
for (String value : values) {
if (containsXss(value)) {
throw new IllegalArgumentException("参数包含非法脚本内容");
}
}
});
}
private boolean containsXss(String value) {
return Arrays.stream(XSS_PATTERNS)
.anyMatch(p -> p.matcher(value).find());
}
}
3. 数据处理层的防护机制
3.1 JSR-303参数校验扩展
在DTO层通过自定义注解实现XSS校验:
java复制@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = XssValidator.class)
public @interface XssSafe {
String message() default "包含非法HTML标签";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class XssValidator implements ConstraintValidator<XssSafe, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) return true;
return value.equals(Jsoup.clean(value, Safelist.none()));
}
}
应用示例:
java复制public class ArticleDTO {
@XssSafe
private String title;
@NotBlank
private String content; // 富文本需特殊处理
}
3.2 MyBatis类型处理器防护
持久层同样需要防护,特别是使用XML方式写SQL时:
java复制@MappedTypes(String.class)
public class XssTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, Jsoup.clean(parameter, Safelist.basic()));
}
// 其他方法实现...
}
在mybatis-config.xml中配置:
xml复制<typeHandlers>
<typeHandler handler="com.example.XssTypeHandler"/>
</typeHandlers>
4. 响应输出层的安全处理
4.1 模板引擎的自动转义
Thymeleaf默认开启HTML转义,但需要注意特殊情况:
html复制<div th:text="${userInput}"></div> <!-- 自动转义 -->
<div th:utext="${trustedHtml}"></div> <!-- 不转义,慎用 -->
FreeMarker需显式配置:
properties复制spring.freemarker.settings.auto_escape=true
4.2 JSON响应的防护措施
使用@ResponseBody时需注意:
java复制@RestControllerAdvice
public class XssResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return returnType.getMethodAnnotation(ResponseBody.class) != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {
return StringEscapeUtils.escapeHtml4((String) body);
}
return body;
}
}
5. 进阶防护与监控方案
5.1 CSP内容安全策略
通过HTTP头定义资源加载策略:
java复制@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'");
return http.build();
}
5.2 审计日志与监控
记录可能的XSS攻击尝试:
java复制@Aspect
@Component
public class XssAuditAspect {
@AfterThrowing(pointcut = "execution(* com.example..*.*(..))",
throwing = "ex")
public void logXssAttempt(IllegalArgumentException ex) {
if (ex.getMessage().contains("非法脚本")) {
log.warn("XSS攻击尝试: {}", ex.getMessage());
// 发送告警通知
}
}
}
6. 实战中的经验总结
- 富文本处理的平衡点:使用
<textarea>+Markdown替代富文本编辑器能大幅降低风险。必须使用富文本时,推荐结合OWASP AntiSamy:
java复制Policy policy = Policy.getInstance("antisamy-ebay.xml");
AntiSamy antiSamy = new AntiSamy();
CleanResults results = antiSamy.scan(dirtyInput, policy);
String safeHtml = results.getCleanHTML();
- 前后端协作要点:
- 前端统一使用
textContent而非innerHTML - 对特殊字符进行编码:
&→&,<→< - 避免直接在JavaScript中拼接HTML
- 测试验证方法:
java复制@Test
void testXssFilter() {
mockMvc.perform(post("/submit")
.param("comment", "<script>alert(1)</script>"))
.andExpect(content().string(not(containsString("<script>"))));
}
- 性能优化技巧:对已知安全的路径(如静态资源)跳过过滤,在高并发场景下可提升20%+的吞吐量。