在SpringMVC中使用拦截器(interceptor)拦截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)验证:

第一次提交表单,发现可以正常到达后台进行验证

第二次点击浏览器的“返回键”,返回到表单页面之后重复提交,可以发现直接被拦截了。效果如下:

wKioL1g85KfCbiusAABp9SVu8Ms567.png



本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1877586,如需转载请自行联系原作者

相关文章
|
26天前
|
JavaScript 安全 前端开发
js开发:请解释什么是XSS攻击和CSRF攻击,并说明如何防范这些攻击。
XSS和CSRF是两种常见的Web安全威胁。XSS攻击通过注入恶意脚本盗取用户信息或控制账户,防范措施包括输入验证、内容编码、HTTPOnly Cookie和CSP。CSRF攻击则诱使用户执行未经授权操作,防范手段有CSRF Tokens、双重验证、Referer检查和SameSite Cookie属性。开发者应采取这些防御措施并定期进行安全审计以增强应用安全性。
19 0
|
5月前
|
安全 NoSQL Java
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
67 0
|
6月前
|
SQL 安全 前端开发
渗透攻击实例-邪恶的CSRF(社会工程学)
渗透攻击实例-邪恶的CSRF(社会工程学)
|
7月前
|
XML JSON 前端开发
SpringMVC进阶-异常拦截器文件上传和Restful风格(1)
SpringMVC进阶-异常拦截器文件上传和Restful风格(1)
30 0
|
7月前
|
前端开发 Java 数据安全/隐私保护
SpringMVC的拦截器和文件上传
SpringMVC的拦截器和文件上传
26 0
|
3月前
|
前端开发 Java Apache
JAVAEE框架技术之6-springMVC拦截器和文件上传功能
JAVAEE框架技术之6-springMVC拦截器和文件上传功能
69 0
JAVAEE框架技术之6-springMVC拦截器和文件上传功能
|
3月前
|
前端开发 Java 应用服务中间件
SpringMvc拦截器和手写模拟SpringMvc工作流程源码详解
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。 M: Model,模型层,指工程中的JavaBean,作用是处理数据。 JavaBean分为两类: 1.实体类Bean:专门存储业务数据的,如Student User等 2.业务处理Bean:指Service或Dao对象,专门用于处理业务逻辑和数据访问。
|
7月前
|
安全 PHP 开发者
CSRF 攻击的防范措施
CSRF 攻击的防范措施
|
3月前
|
安全 前端开发 Java
什么是 CSRF 攻击?
什么是 CSRF 攻击?
|
3月前
|
安全 JavaScript 前端开发
Python 的安全性和测试:解释什么是 XSS 和 CSRF 攻击?在 Python 中如何防范这些攻击?
Python 的安全性和测试:解释什么是 XSS 和 CSRF 攻击?在 Python 中如何防范这些攻击?