当前位置: 首页 > news >正文

在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,如需转载请自行联系原作者

相关文章:

  • 一、网络的基本概念
  • 利用LVS-NAT和DR模型分别负载均衡一个php应用
  • VII Python(9)socket编程
  • zookeeper系列(七)实战分布式命名服务
  • Animations的使用
  • 利用Python生成随机4位验证码
  • 测试标准学习
  • 一些不常见的css知识
  • 第四课——MFC应用程序框架
  • 列表、元祖概述
  • 拨云见日—深入解析Oracle TX 行锁(上)
  • 询问Spring Bott和高并发框架两个问题
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • MicroProfile 1.2新增功能介绍
  • Google瓦片地图算法解析
  • [PHP内核探索]PHP中的哈希表
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Javascript基础之Array数组API
  • js对象的深浅拷贝
  • learning koa2.x
  • uni-app项目数字滚动
  • Vim Clutch | 面向脚踏板编程……
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 缓存与缓冲
  • 检测对象或数组
  • 简单数学运算程序(不定期更新)
  • 聊聊redis的数据结构的应用
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 使用putty远程连接linux
  • 手机端车牌号码键盘的vue组件
  • mysql面试题分组并合并列
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • #Linux(Source Insight安装及工程建立)
  • $forceUpdate()函数
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (c语言)strcpy函数用法
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (Java数据结构)ArrayList
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (一)SpringBoot3---尚硅谷总结
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .form文件_一篇文章学会文件上传
  • @Repository 注解
  • [ 数据结构 - C++] AVL树原理及实现
  • [AIGC codze] Kafka 的 rebalance 机制
  • [AIR] NativeExtension在IOS下的开发实例 --- IOS项目的创建 (一)
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [BetterExplained]书写是为了更好的思考(转载)