在Spring Boot中,我们可以使用注解的方式来进行XSS防御。注解是一种轻量级的防御手段,它可以在方法或字段级别对输入进行校验,从而防止XSS攻击。
引入相关依赖
maven依赖:
<!--JSR-303/JSR-380用于验证的注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.6.7</version>
</dependency>
如果是使用grade,引入依赖:
implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.7'
定义@XSS注解进行参数校验
我们可以自定义一个@XSS注解,用于标记那些需要校验的参数。这里是一个简单的@XSS注解定义:
package com.morris.spring.boot.module.xss;
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;
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = XssValidator.class)
public @interface Xss {
String message() default "非法输入, 检测到潜在的XSS";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
实现自定义注解处理器
接下来,我们需要实现XSSValidator类,该类将负责检查输入是否包含潜在的XSS攻击脚本:
package com.morris.spring.boot.module.xss;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* xss注解校验
*/
public class XssValidator implements ConstraintValidator<Xss, String> {
/**
* 使用jsoup自带的relaxed白名单
*/
private static final Whitelist WHITE_LIST = Whitelist.relaxed();
/**
* 定义输出设置,关闭prettyPrint(prettyPrint=false),目的是避免在清理过程中对代码进行格式化
* 从而保持输入和输出内容的一致性。
*/
private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);
/**
* 验证输入值是否有效,即是否包含潜在的XSS攻击脚本。
*
* @param value 输入值,需要进行XSS攻击脚本清理。
* @param context 上下文对象,提供关于验证环境的信息,如验证失败时的错误消息定制。
* @return 如果清理后的值与原始值相同,则返回true,表示输入值有效;否则返回false,表示输入值无效。
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 这里先对value进行一次解析,对其中的单个<、>字符进行编码
String oldBody = Jsoup.parse(value).body().html();
// 使用Jsoup库对输入值进行清理,以移除潜在的XSS攻击脚本。
// 使用预定义的白名单和输出设置来确保只保留安全的HTML元素和属性。
String newBody = Jsoup.clean(value, "", WHITE_LIST, OUTPUT_SETTINGS);
// 比较清理后的值与解析后的值是否相同,不相同说明过滤了xss脚本
// 这里不使用value与newBody比,因为jsoup会对value中的单个<、>字符进行编码,如果value中还有单个的<,value与newBody也会不一样
return oldBody.equals(newBody);
}
}
使用@Xss注解防御post请求
如果是post请求,需要在方法参数前面加上@Valid或者@Validated注解,然后在实体类的属性上面加上@Xss注解。
package com.morris.spring.boot.module.xss;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* Xss局部防御post请求
*/
@RestController
@RequestMapping("/xss/local")
public class XssLocalPostController {
/**
* 使用注解拦截POST请求中的xss,在实体类需要拦截xss的属性上面加上@Xss或者@Validated注解
*
* @param userLocalLoginPojo 实体类
* @return 实体类
*/
@PostMapping("/test")
public UserLocalLoginPojo test(@Valid @RequestBody UserLocalLoginPojo userLocalLoginPojo) {
return userLocalLoginPojo;
}
}
在要进行XSS防御的属性上添加@Xss注解:
package com.morris.spring.boot.module.xss;
import lombok.Data;
@Data
public class UserLocalLoginPojo {
@Xss
private String userAccount;
}
测试url:http://localhost:8888/xss/local/test
测试post请求:
{
"userAccount": "<iframe οnlοad='alert(0)'>demoData</iframe>"
}
测试结果:
{
"message": "userAccount:非法输入, 检测到潜在的XSS",
"code": 400,
"result": null
}
使用@Xss注解防御get请求
如果是get请求,需要在方法参数前面加上@Xss注解,然后在类上加上@Validated注解。
package com.morris.spring.boot.module.xss;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* Xss局部防御get请求
*/
@RestController
@RequestMapping("/xss/local")
@Validated
public class XssLocalGetController {
/**
* 使用注解拦截get请求中的xss,在方法参数前面加上@Xss,注意类上面要加上@Validated注解
*
* @param userAccount 请求参数
* @return 请求参数
*/
@GetMapping("/test")
public String test(@Xss String userAccount) {
return userAccount;
}
}
测试url:http://localhost:8888/xss/local/test?userAccount=<iframe>demoData</iframe>
测试结果:
{
"message": "test.userAccount: 非法输入, 检测到潜在的XSS",
"code": 400,
"result": null
}