CSRF spring mvc 跨站请求伪造防御(转)

CSRF 
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

攻击示例 
如:一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后者刚刚发布了一个具有Bob银行链接的图片消息。设想一下,Alice编写了一个在Bob的银行站点上进行取款的form提交的链接,并将此链接作为图片src。如果Bob的银行在cookie中保存他的授权信息,并且此cookie没有过期,那么当Bob的浏览器尝试装载图片时将提交这个取款form和他的cookie,这样在没经Bob同意的情况下便授权了这次事务。 
CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。在上面银行示例中的代理人是Bob的web浏览器,它被混淆后误将Bob的授权直接交给了Alice使用。 
下面是CSRF的常见特性: 
依靠用户标识危害网站 
利用网站对用户标识的信任 
欺骗用户的浏览器发送HTTP请求给目标站点 
另外可以通过IMG标签会触发一个GET请求,可以利用它来实现CSRF攻击。

spring mvc 框架下防御策略

思路概要: 
1.初始化页面时在token隐藏域。 
2.表单提交后带入token到后台,验证token,如成功继续操否为受到攻击。 
3.操作完之后重新生成token到页面隐藏域。 
代码示例: 
创建拦截器:

/**
 * <一句话功能简述>
 * <功能详细描述>
 * 防止跨站请求伪造拦截器
 * 为每个返回的页面添加CSRFToken参数
 * @author  Tangguilin
 * @version  [版本号, 2016年3月26日]
 */
public class AvoidCSRFInterceptor extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception
    {
        String url = request.getRequestURI();
        if (!url.endsWith(".do"))
        {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        VerifyCSRFToken annotation = method.getAnnotation(VerifyCSRFToken.class);
        if (annotation != null)
        {
            String xrq = request.getHeader("X-Requested-With");//是否为Ajax标志
            //非法的跨站请求校验
            if (annotation.verifyCSRFToken() && !verifyCSRFToken(request))
            {
                if (StringUtil.isEmpty(xrq))
                {
                    //form表单提交,url get方式,刷新csrftoken并跳转提示页面
                    request.getSession(false).setAttribute("CSRFToken",
                        TokenProcessor.getInstance().generateToken(request));
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print("非法请求");
                    response.flushBuffer();
                    return false;
                }
                else
                {
                    //刷新CSRFToken,返回错误码,用于ajax处理,可自定义
                    BaseDataResp baseResp = new BaseDataResp();
                    String csrftoken = TokenProcessor.getInstance().generateToken(request);
                    request.getSession(false).setAttribute("CSRFToken", csrftoken); 
                    baseResp.setCode(IDiMengResultCode.SystemManager.CSRF);
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print(baseResp.toString());
                    response.flushBuffer();
                    return false;
                }
            }

        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView)
            throws Exception
    {

        String url = request.getRequestURI();
        if (!url.endsWith(".do"))
        {
            return;
        }

        //第一次生成token
        if (modelAndView != null)
        {
            if (request.getSession(false) == null
                || StringUtil.isEmpty((String)request.getSession(false).getAttribute("CSRFToken")))
            {
                request.getSession(false).setAttribute("CSRFToken",
                    TokenProcessor.getInstance().generateToken(request));
                return;
            }
        }
        //刷新token
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);
        String xrq = request.getHeader("X-Requested-With");//是否为Ajax标志
        if (refreshAnnotation != null && refreshAnnotation.refreshCSRFToken() && StringUtil.isEmpty(xrq))
        {
            request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request));
            return;
        }
        VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);
        if (verifyAnnotation != null)
        {
            //成功后刷新token
            if (verifyAnnotation.verifyCSRFToken())
            {
                if (StringUtil.isEmpty(xrq))
                {
                    request.getSession(false).setAttribute("CSRFToken",
                        TokenProcessor.getInstance().generateToken(request));
                }
                else
                {
                    Map<String, String> map = new HashMap<String, String>();
                    map.put("CSRFToken", TokenProcessor.getInstance().generateToken(request));
                    response.setContentType("application/json;charset=UTF-8");
                    OutputStream out = response.getOutputStream();
                    out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8"));
                }
            }
        }
    }

    /** <一句话功能简述>
     * 处理跨站请求伪造
     * 针对需要登录后才能处理的请求,验证CSRFToken校验
     * @author  tangguilin
     * @param request
     */
    protected boolean verifyCSRFToken(HttpServletRequest request)
    {
        String requstCSRFToken = request.getHeader("CSRFToken");//请求中的CSRFToken
        if (StringUtil.isEmpty(requstCSRFToken))
        {
            return false;
        }
        String sessionCSRFToken = (String)request.getSession().getAttribute("CSRFToken");
        if (StringUtil.isEmpty(sessionCSRFToken))
        {
            return false;
        }
        return requstCSRFToken.equals(sessionCSRFToken);
    }
}
  • 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
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 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
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

注解类:

/**
 * 跨站请求仿照注解
 * 刷新CSRFToken
 * @author  Tangguilin
 * @version  [版本号, 2016年3月28日]
 */
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RefreshCSRFToken
{
    /** 
     * 刷新CSRFToken
     * @author tangguilin
     * @return
     */
    public abstract boolean refreshCSRFToken();
}

/**
 * 跨站请求仿照注解
 * 
 * @author  Tangguilin
 * @version  [版本号, 2016年3月28日]
 */
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifyCSRFToken
{
    /** 
     * 需要验证防跨站请求
     * @author tangguilin
     * @return
     */
    public abstract boolean verifyCSRFToken();

}
  • 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
  • 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

配置拦截:

   <mvc:interceptors>   
        <mvc:interceptor>  
            <mvc:mapping path="/**"/> 
            <!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 -->  
           <bean class="com.front.interceptor.AvoidCSRFInterceptor"/>  
        </mvc:interceptor>  
    </mvc:interceptors> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在jsp页面中添加隐藏域

 <input type="hidden" name="CSRFToken" value="${CSRFToken }"></input>  
  • 1
  • 1

修改公用Ajax

 ajax: function(options) {
            var datas = null;
            if (options["isAjaxForm"]) {
                $("#" + options["formId"]).submit();
            } else {
                if (options["serialize"]) {
                    if (options["formId"]) {
                        datas = $("#" + options["formId"]).serialize();
                    } else {
                        datas = $(document.forms[0]).serialize();
                    }
                }
                var defaultOptions = {
                    type: "post",
                    async: false,
                    data: datas
                };
                options = $.extend(defaultOptions, options);
                var path="";
                if(options["url"].indexOf(basePath)==-1){
                    path=basePath;
                }
                var headers = {};
                headers['CSRFToken'] = $("input[name='CSRFToken']").val();
                $.ajax({
                    type: options["type"],
                    headers: headers,
                    async: options["async"],
                    dataType: options["dataType"],
                    url: path + options["url"],
                    data: options["data"],
                    success: function(data) {
                        //登录超时刷新页面后跳转首页
                        if("2000062"==data.code){
                            window.location.reload();
                        }
                        //处理跨站请求伪造
                        if("666666" == data.code){
                            if(Dialog){
                                Dialog.show("操作失败,请刷新页面重试","error");
                            }else{
                                alert("操作失败,请刷新页面重试");
                            }
                            return false;
                        }
                        if (typeof(options["success"]) == "function") {
                            options["success"](data);
                        }
                    },
                    error: function(data) {
                        //CSRFToken处理
                        if(data && data.readyState && data.readyState == '4'){
                            var responseBody = data.responseText;
                            if(responseBody){
                                responseBody = "{'retData':"+responseBody;
                                var resJson = eval('(' + responseBody + ')');
                                $("input[name='CSRFToken']").val(resJson.csrf.CSRFToken);
                                if (typeof(options["success"]) == "function") {
                                    options["success"](resJson.retData);
                                }
                            }
                            return ;
                        }
                        //登录超时跳转登录页
                        /*if(data.responseText){
                             window.location.reload();//刷新当前页面.
                            return;
                        }*/
                        if (typeof(options["error"]) == "function") {
                            options["error"](data);
                        }else{
                            alert("请求地址:"+options["url"]+"  出现异常!请联系管理员!");
                        }

                    }
                });
            }
        }
  • 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
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 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
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

使用场景 
为需要防御的Controller加上@VerifyCSRFToken(verifyCSRFToken = true)注解

@RefreshCSRFToken(refreshCSRFToken=true):如新打开的页面,弹出框体需要CSRFToken时使用

原文地址:https://www.cnblogs.com/bmaker/p/5805217.html