关于什么是CSRF我这里就不多说了,以前转载的一篇文章(PS:https://www.zifangsky.cn/358.html)已经说得很清楚了。这里只是简单介绍如何在SpringMVC中使用拦截器拦截CSRF攻击。具体代码如下:
(1)登录页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <%@page import="java.security.SecureRandom"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <base href="<%=basePath%>"> <title>SpringMVC Cookie Demo</title> <% SecureRandom random = new SecureRandom(); random.setSeed(8738); double _csrf = random.nextDouble(); session.setAttribute("_csrf", _csrf); %> </head> <body> <div align="center"> <h2>SpringMVC Cookie Demo</h2> <form action="check.html" method="post"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" /></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password" /></td> </tr> <tr> <td><input name="remember-me" type="checkbox">30天内自动登录</input></td> </tr> <tr> <td colspan="2" align="center"><input type="submit" value="登录" /> <input type="reset" value="重置" /></td> </tr> </table> <input type="hidden" name="_csrf" value="<%=_csrf %>" /> </form> </div> </body> </html> |
从上面的代码可知,为了防止CSRF攻击,因此在form表单里添加了一个隐藏字段“_csrf”,其值是生成的一个随机小数
(2)在SpringMVC的配置文件中添加拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd" default-lazy-init="true"> <mvc:annotation-driven /> <!-- 组件扫描 --> <context:component-scan base-package="cn.zifangsky.controller" /> <context:component-scan base-package="cn.zifangsky.manager.impl"/> <!-- 配置直接转发的页面 --> <mvc:view-controller path="/login.html" view-name="login" /> <mvc:view-controller path="/user/callback.html" view-name="user/callback" /> <!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 对登录操作进行拦截 --> <mvc:mapping path="/check.html"/> <bean class="cn.zifangsky.interceptor.LoginInterceptor" /> </mvc:interceptor> <mvc:interceptor> <!-- 对/user/**的请求进行拦截 --> <mvc:mapping path="/user/**"/> <bean class="cn.zifangsky.interceptor.UserInterceptor" /> </mvc:interceptor> </mvc:interceptors> <!-- 视图解析 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/" /> <property name="suffix" value=".jsp" /> </bean> </beans> |
从上面的代码知道,在这个文件中添加了一个 mvc:interceptors 标签,表示一系列的拦截器集合,然后下面定义了对登录时form表单提交地址“/check.html”进行拦截。下面一行的bean属性就是定义了自定义拦截器的类所在的路径
注:后面那个拦截器这里不用管,我在写后面的文章时才会用到
(3)自定义拦截器LoginInterceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package cn.zifangsky.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class LoginInterceptor extends HandlerInterceptorAdapter { /** * 用于在登录前验证 _csrf 参数 * */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); String _csrfByForm = request.getParameter("_csrf"); //表单中的值 String _csrfBySession = String.valueOf(session.getAttribute("_csrf")); //session中的值 session.removeAttribute("_csrf"); //使用之后从session中删掉 //验证是否存在CSRF攻击 if(StringUtils.isNotBlank(_csrfByForm) && StringUtils.isNotBlank(_csrfBySession) && _csrfByForm.equals(_csrfBySession)){ return true; }else{ response.setContentType("text/html;charset=utf-8"); response.setStatus(403); //页面友好提示信息 OutputStream oStream = response.getOutputStream(); oStream.write("请不要重复提交请求,返回原始页面刷新后再次尝试!!!".getBytes("UTF-8")); return false; } } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { super.afterCompletion(request, response, handler, ex); } } |
这个自定义拦截器的逻辑很简单,就是把form表单隐藏域“_csrf”中的值和session中的“_csrf”值进行比较。如果二者相同,则说明该请求是从前台form表单中传进来的,而不是其他网站的伪造请求(PS:因为这种方式没法向session中定义“_csrf”参数);同时也防止form表单的重复提交(PS:因为第一次验证过后session中的“_csrf”就已经被移除了,除非前台刷新页面才会重新生成),避免了爆破撞库等安全隐患。当然,为了进一步降低安全隐患,这里的form表单还应该添加复杂的动态验证码。我这里是由于为了让示例更简洁,因此就把这一步给省略了
(4)验证:
第一次提交表单,发现可以正常到达后台进行验证
第二次点击浏览器的“返回键”,返回到表单页面之后重复提交,可以发现直接被拦截了。效果如下:
注:此次修改添加了重复提交表单时的友好提示信息
陈田村 2018/01/25 11:14
谢谢分享,站长新年快乐
admin 博主 2018/01/25 13:56
@ 谢谢,同乐!
自行车 2016/09/23 14:05
都是英文字母,看不懂
admin 博主 2016/09/23 16:14
@ 哪有,不是还有注释吗?
金堂装修设计 2016/09/20 22:33
目前无法企及的技术
admin 博主 2016/09/21 00:53
@ 呃,过奖了
卢松松博客 2016/09/18 16:29
从百度点进来的,支持一下,希望站长您多出一些好文章。
admin 博主 2016/09/18 21:47
@ 谢谢
跨境电商平台 2016/09/24 00:05
@ MARK收藏。。。