赞
踩
本文使用JSP验证效果
项目结构
引人依赖
<!-- springboot-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <!-- commons-text --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.6</version> </dependency> <!-- JSP依赖 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency>
用户注册页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/info" method="post">
用户名:<input type="text" name="username">
密码:<input type="text" name="password">
昵称:<input type="text" name="nickname">
描述:<input type="text" name="description">
<input type="submit" value="提交">
</form>
</body>
</html>
用户注册成功后显示用户信息页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>用户名:${user.username}</p>
<p>密码:${user.password}</p>
<p>昵称:${user.nickname}</p>
<p>描述:${user.description}</p>
</body>
</html>
用户实体对象
package com.dfyang.xss.entity; /** * @Auther: 55411 * @Date: 2019/6/21 16:20 * @Description: 用户实体 */ public class User { private String username; private String password; private String nickname; private String description; /* 省略get、set */ }
编写测试Controller
package com.dfyang.xss.controller; import com.dfyang.xss.entity.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; /** * @Auther: 55411 * @Date: 2019/6/21 14:19 * @Description: 测试Controller */ @Controller public class XSSController { @GetMapping("/register") public String insert() { return "register"; } @PostMapping("/info") public String info(User user, Model model) { model.addAttribute("user", user); return "info"; } }
启动项目,访问 http://localhost:8080/register (请使用火狐浏览器进行测试)
这里再昵称和描述输入了 <script>alert(123);</script>
下面窗口弹出了两次。
创建@PreventXSSMethod注解,用在需要AOP拦截的Controller方法上
package com.dfyang.xss.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Auther: 55411 * @Date: 2019/6/21 11:48 * @Description: 注解在方法上,表示该方法中的参数需要预防XSS */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PreventXSSMethod { }
创建@PreventXSSParameter注解,用在拦截方法的参数上,表示该参数需要防XSS攻击。
package com.dfyang.xss.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Auther: 55411 * @Date: 2019/6/21 15:30 * @Description: 注解在参数上,表示该参数需要预防XSS */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface PreventXSSParameter { }
创建@PreventXSSField注解,如果参数是String类型,我们希望直接进行格式化,但很多情况下,我们的参数并不是一个String类型,比如上面的User对象。所以在需要防XSS且非String类型的对象,在需要防XSS的字段上加@PreventXSSField声明需要防XSS攻击。
package com.dfyang.xss.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Auther: 55411 * @Date: 2019/6/21 16:20 * @Description: 注解在字段上,表示该字段需要预防XSS */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface PreventXSSField { }
最后编写我们的AOP代码。
(1)首先,我们将拦截所有加了@PreventXSSMethod的方法。
(2)然后,获取加了@PreventXSSParameter注解1的参数。
(3)如果参数类型为String,直接进行格式化。
(4)如果参数类型不是String,通过反射获取该参数的所有字段。
(5)对加了@PreventXSSField注解的字段进行格式化。
(6)将修改后的参数返回给原方法。
package com.dfyang.xss.aop; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; 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.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * @Auther: 55411 * @Date: 2019/6/21 11:50 * @Description: AOP防XSS攻击 */ @Aspect @Component public class PreventXSSAspect { /** * 拦截controller路径下所有类的所有方法 */ @Pointcut("execution(* com.dfyang.xss.controller.*.*(..))") public void pointCut() {} @Around("pointCut() && @annotation(preventXSSMethod)") public Object around(ProceedingJoinPoint joinPoint, PreventXSSMethod preventXSSMethod) 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 PreventXSSParameter){ // 如果是String类型,将其进行格式化 // 否则获取该类型的所有字段,对String类型的字段进行格式化 if (args[i] instanceof String && StringUtils.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(PreventXSSField.class) != null) { // 如果是字段不可访问,设置临时可访问 if (!field.isAccessible()) field.setAccessible(true); // 如果字段是字符串类型则进行格式化 Object fieldValue = field.get(args[i]); if (fieldValue instanceof String && StringUtils.isNotEmpty((String) fieldValue)) field.set(args[i], format((String) fieldValue)); } } } } } } // 将参数覆盖到到原方法 Object proceed = joinPoint.proceed(args); return proceed; } /** * 对需要防范的字符串进行格式化 */ public String format(String xssStr) { return StringEscapeUtils.escapeHtml4(xssStr); } }
这里使用的StringEscapeUtils,如果需要自定义防XSS,只需从写上面的format方法即可。
——如果没看懂的朋友建议打断点。
最后在启动类上加上注解
package com.dfyang.xss; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication @EnableAspectJAutoProxy public class XssApplication { public static void main(String[] args) { SpringApplication.run(XssApplication.class, args); } }
接下来修改代码,防XSS攻击。
对于User对象,当我们注册时,可能并不希望用户输入乱七八糟的字符,所以对于用户名和密码,当用户输入非法字符时要求重新输入即可,不需要防XSS。这里仅对昵称和描述防XSS。
public class User {
private String username;
private String password;
@PreventXSSField
private String nickname;
@PreventXSSField
private String description;
}
在info方法加上@PreventXSSMethod表示该方法需要防XSS,在user参数加上@PreventXSSParameter表示该参数需要防XSS。
@PreventXSSMethod
@PostMapping("/info")
public String info(@PreventXSSParameter User user, Model model) {
model.addAttribute("user", user);
return "info";
}
启动项目,验证代码——
同样输入上面代码。
如果有不正确或需要改进的地方,欢迎指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。