前言
上章通过Filter实现了Xss全局过滤器
可能小伙伴还有点不满, 全局意味着“一刀切”,
虽然我们也有白名单黑名单设置, 但是, 白名单黑名单针对的是整个方法或整个实体类
举个例子, 我定义了个实体
public class People {
private String name;
private String info;
private String des;
}
可能业务上有限制(比如name限制了只有5个字符长),
那么name其实不可能存在Xss注入风险了,
程序只需要对info和des属性做转义即可
如果采用Filter, 意味着People的所有属性都会检测转义, 好像没啥必要!
那么, 可以针对性的指定字段进行Xss的过滤转义么?
为了应对这样的需求, 咸鱼君采用Aop注解来实现这么个Xss过滤器.
PS: 和Filter一样, 我们统一对入参Xss过滤, 确保参数的处理方式统一! 所以, 程序中获取的参数值都是转义后的数据!!
废话不多说, 往下看!
代码实现
加入必要的pom依赖
<!--Aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--HuTool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 实用工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
针对方法, 参数, 属性分别定义三个注解
XssMethod
/**
* @Description: 注解在方法上,表示该方法中的参数需要预防XSS
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XssMethod {
}
XssParam
/**
* @Description: 注解在参数上,表示该参数需要预防XSS
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface XssParam {
}
XssField
/**
* @Description: 注解在字段上,表示该字段需要预防XSS
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XssField {
}
针对注解实现切面Aspect
XssAspect
/**
* @Description: AOP防XSS攻击
*/
@Aspect
@Slf4j
@Component
public class XssAspect {
@PostConstruct
public void init() {
log.info("XssAspect init ......");
}
@Around("@annotation(xssMethod)")
public Object around(ProceedingJoinPoint joinPoint, XssMethod xssMethod) throws Throwable {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取所有参数上的注解(一个参数可能对应多个注解,因此获取到的是一个二维数组。)
Annotation[][] paramsAnnotations = signature.getMethod().getParameterAnnotations();
// 遍历一维数组,获取每个参数对应的注解数组
for (int i = 0; i < paramsAnnotations.length; i++) {
Annotation[] paramAnnotations = paramsAnnotations[i];
// 遍历每个参数的注解数组
for (Annotation annotation : paramAnnotations) {
// 如果参数需要预防XSS攻击
if (annotation instanceof XssParam) {
// 如果是String类型,将其进行格式化
// 否则获取该类型的所有字段,对String类型的字段进行格式化
if (args[i] instanceof String && StrUtil.isNotEmpty((String) args[i])) {
args[i] = format((String) args[i]);
} else {
Class clazz = args[i].getClass();
// 获取类的所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 如果字段上有@PreventXSSField注解
if (field.getDeclaredAnnotation(XssField.class) != null) {
// 如果是字段不可访问,设置临时可访问
if (!field.isAccessible()) {
field.setAccessible(true);
}
// 如果字段是字符串类型则进行格式化
Object fieldValue = field.get(args[i]);
if (fieldValue instanceof String && StrUtil.isNotEmpty((String) fieldValue)) {
field.set(args[i], format((String) fieldValue));
}
}
}
}
}
}
}
// 将参数覆盖到到原方法
Object proceed = joinPoint.proceed(args);
return proceed;
}
/**
* 对需要防范的字符串进行格式化
*/
public String format(String xssStr) {
return EscapeUtil.escape(xssStr);
}
}
注释写的很详细, 就不多废话了!
接下来我们写个案例来测试测试
首先在启动类上启用切面
@SpringBootApplication
@EnableAspectJAutoProxy
public class XssaopApplication {
public static void main(String[] args) {
SpringApplication.run(XssaopApplication.class, args);
}
}
定义一个实体People, 并对info,des属性加上注解进行xss过滤
@Setter
@Getter
public class People {
private String name;
@XssField
private String info;
@XssField
private String des;
}
最后编写controller
@RestController
@Slf4j
public class XssController {
@PostMapping("xssFilter")
@XssMethod
public String xssFilter(@XssParam String name, String info) {
log.error(name + "-----" + info);
return name + "---" + info;
}
@XssMethod
@PostMapping("modelXssFilter")
public People modelXssFilter(@RequestBody @XssParam People people) {
log.error(people.getName() + "-----" + people.getInfo());
return people;
}
}
json方式
键值对方式
项目地址
https://github.com/MrCoderStack/SpringBootDemo/tree/master/sb-xssaop