防CSRF攻击:一场由重复提交的问题引发的前端后端测试口水战

简介: 重复提交,这是一直以来都会存在的问题,当在网站某个接口调用缓慢的时候就会有可能引起表单重复提交的问题,不论form提交,还是ajax提交都会有这样的问题,最近在某社交app上看到这么一幕,这个团队没有做重复提交的验证,从而导致了数据有很多的重复提交,在这里我们不讨论谁对谁错,问题解决即可。

重复提交,这是一直以来都会存在的问题,当在网站某个接口调用缓慢的时候就会有可能引起表单重复提交的问题,不论form提交,还是ajax提交都会有这样的问题,最近在某社交app上看到这么一幕,这个团队没有做重复提交的验证,从而导致了数据有很多的重复提交,在这里我们不讨论谁对谁错,问题解决即可。

 

 首先的一种方式,在前端加入loading,或者是blockUI,在ios以及安卓上也是类似,效果如下: 

这个时候整个页面不能再用鼠标点击,只能等待请求响应以后才能操作

具体可以参考blockUI这个插件

此外就是后端了,其实后端在一定程度上也要进行防止重复提交的验证,某些无所谓的情况下可以在前端加,某些重要的场景下比如订单等业务就必须再前后端都要做,为了测试方便,blockUI就直接注释

在后台我们线程sleep5秒

 

@RequestMapping("/CSRFDemo/save")
    @ResponseBody
    public LeeJSONResult saveCSRF(Feedback feedback) {
        
        log.info("保存用户反馈成功, 入参为 Feedback.title: {}", feedback.getTitle());
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        return LeeJSONResult.ok();
    }

多次点击,效果如下

 

 

步骤1:页面生成token,每次进入都需要重新生成

设置自定义标签

package com.javasxy.web.tag;

import java.io.IOException;
import java.io.StringWriter;
import java.util.UUID;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;

import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.utils.SpringUtils;

/**
 * 
 * @Title: TokenTag.java
 * @Package com.javasxy.web.tag
 * @Description: 生成页面token
 * Copyright: Copyright (c) 2016
 * Company:DINGLI.SCIENCE.AND.TECHNOLOGY
 * 
 * @author leechenxiang
 * @date 2017年4月11日 下午3:29:13
 * @version V1.0
 */
public class TokenTag extends SimpleTagSupport {

    private String id;

    private String name;
    
    private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_";

    StringWriter sw = new StringWriter();
    
    private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class);
    
    public void doTag() throws JspException, IOException {
        
        // 生成token
        String token = UUID.randomUUID().toString();
        
        // 构建token隐藏框
        String tokenHtml = "<input type='hidden' ";
        if (StringUtils.isNotEmpty(id)) {
            tokenHtml += " id='" + id + "' ";
        } 
        if (StringUtils.isNotEmpty(name)) {
            tokenHtml += " name='" + name + "' ";
        } 
        tokenHtml += " value='" + token + "' ";
        tokenHtml += " />";
        
        // 获取当前登录用户信息
        ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal();
        // 设置token到redis(如果是单应用项目设置到session中即可)
        jedis.set(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), token);
        jedis.expire(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername(), 1800);
        
        JspWriter out = getJspContext().getOut();
        out.println(tokenHtml);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

页面生成

 

 查看redis

 拦截器代码:

 

package com.javasxy.web.controller.interceptor;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.javasxy.common.utils.JsonUtils;
import com.javasxy.common.utils.LeeJSONResult;
import com.javasxy.common.utils.NetworkUtil;
import com.javasxy.components.JedisClient;
import com.javasxy.pojo.ActiveUser;
import com.javasxy.web.controller.filter.ShiroFilterUtils;
import com.javasxy.web.utils.SpringUtils;

public class CSRFTokenInterceptor extends HandlerInterceptorAdapter {
    
    final static Logger log = LoggerFactory.getLogger(CSRFTokenInterceptor.class);
    
    private static final String CACHE_MAKE_CSRF_TOKEN_ = "cache_make_csrf_token_";

    private JedisClient jedis = SpringUtils.getContext().getBean(JedisClient.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        // 获取从页面过来的token
        String pageCSRFToken = request.getHeader("pageCSRFToken");
        
        // 获取IP
        String ip = NetworkUtil.getIpAddress(request);
        if (StringUtils.isEmpty(pageCSRFToken)) {
            String msg = "禁止访问";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        // 当前登录用户
        ActiveUser activeUser = (ActiveUser)SecurityUtils.getSubject().getPrincipal();
        
        String CSRFToken = jedis.get(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
        if (StringUtils.isEmpty(CSRFToken)) {
            String msg = "操作频繁,请稍后再试";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        if (!pageCSRFToken.equals(CSRFToken)) {
            String msg = "禁止访问";
            log.error("ip: {}, errorMessage: {}", ip, msg);
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(msg));
            return false;
        }
        
        // 清除token
        jedis.del(CACHE_MAKE_CSRF_TOKEN_ + ":" + activeUser.getUsername());
        return true;
    }
    
    public void returnErrorResponse(HttpServletResponse response, LeeJSONResult result) throws IOException, UnsupportedEncodingException {
        OutputStream out=null;
        try{
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/json");
            out = response.getOutputStream();
            out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
            out.flush();
        } finally{
            if(out!=null){
                out.close();
            }
        }
    }
    
    /*public boolean returnError(HttpServletRequest request, HttpServletResponse response, String errorMsg) throws IOException, UnsupportedEncodingException {
        if (ShiroFilterUtils.isAjax(request)) {
            returnErrorResponse(response, LeeJSONResult.errorTokenMsg(errorMsg));
            return false;
        } else {
            // TODO 跳转页面
            return false;
        }
    }*/

}

测试:

 

这样重复提交的问题就解决了,同时也解决了CSRF攻击的问题,关于什么是CSRF可以自行百度

注意:

1、token生成也可以在异步调用的时候生成,也就是一次请求一个token,而不是一个页面一个token,但是这样做可能会被第三方获取

2、这里使用了springmvc的拦截器,当然在shiro中也可以自定义过滤器来实现,代码略

 

相关文章
|
1天前
|
前端开发 Java Go
从前端到后端:构建现代化Web应用的技术演进
本文探讨了从前端到后端的技术演进,介绍了前端、后端以及多种编程语言,如Java、Python、C、PHP和Go,以及数据库在构建现代化Web应用中的应用。通过深入剖析各个技术领域的发展和应用,读者将对构建高效、可扩展、安全的Web应用有更深入的理解。
|
4天前
|
前端开发 Java Go
从前端到后端:构建现代化Web应用的技术实践
本文将介绍如何通过前端和后端技术相结合,构建现代化Web应用的技术实践。我们将探讨前端开发、后端架构以及多种编程语言(如Java、Python、C、PHP、Go)在构建高效、可扩展的Web应用中的应用。
|
4天前
|
前端开发 JavaScript Java
npm与Maven:前端与后端构建工具深度对比学习
npm与Maven:前端与后端构建工具深度对比学习
|
5天前
|
前端开发 JavaScript NoSQL
从前端到后端:构建全栈开发者的必备技能
随着互联网技术的不断发展,全栈开发者的需求日益增长。本文将介绍如何从前端到后端,掌握全栈开发所需的关键技能,包括前端框架的选择、后端语言的学习以及数据库的应用,帮助读者构建成为全面的技术专家。
|
6天前
|
前端开发 测试技术 持续交付
《跨界合作:前端与后端如何优化协作效率》
在当今软件开发领域,前端和后端开发团队通常是分开工作的,但他们的协作质量直接影响着项目的成功与否。本文将探讨如何通过优化前端与后端的协作方式,提高开发效率和项目质量,从而实现更好的跨界合作。
|
6天前
|
JSON JavaScript 前端开发
vue的 blob文件下载文件时,后端自定义异常,并返回json错误提示信息,前端捕获信息并展示给用户
vue的 blob文件下载文件时,后端自定义异常,并返回json错误提示信息,前端捕获信息并展示给用户
|
6天前
|
前端开发 小程序 测试技术
前端后端测试接口mork神器,Apifox使用一分钟入门
前端后端测试接口mork神器,Apifox使用一分钟入门
14 0
|
6天前
|
存储 安全 前端开发
PHP医院安全不良事件管理系统源码(AEMS)前端vue2+element+后端laravel8不良事件上报与闭环管理
医院不良事件上报与管理系统结合现代医院管理思路,遵照PDCA全面质量循环管理方法而设计,并在多家大型三甲医院成熟运用。系统从事件上报、基于人、机、料、法 、环的RCA分析、事件整改、效果评估实现了结构化、标准化、智能化的管理和分析,满足医院可追溯化、全流程闭环管理要求,满足等级医院评审细则要求,大力提高医院不良事件上报的效率,保障事件分析的准确性,促进医疗安全的提高,避免同类事件再次发生,改善整个医院医疗安全,从而实现医院安全医疗的目标。
27 3
|
6天前
|
JavaScript 安全 前端开发
js开发:请解释什么是XSS攻击和CSRF攻击,并说明如何防范这些攻击。
XSS和CSRF是两种常见的Web安全威胁。XSS攻击通过注入恶意脚本盗取用户信息或控制账户,防范措施包括输入验证、内容编码、HTTPOnly Cookie和CSP。CSRF攻击则诱使用户执行未经授权操作,防范手段有CSRF Tokens、双重验证、Referer检查和SameSite Cookie属性。开发者应采取这些防御措施并定期进行安全审计以增强应用安全性。
27 0
|
6月前
|
安全 NoSQL Java
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
互联网并发与安全系列教程(06) - 常见的Web安全漏洞(CSRF攻击)
75 0