Spring mvc拦截器防御CSRF攻击

CSRF(具体参考百度百科)

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

 

具体思路:

1、跳转页面前生成随机token,并存放在session中

2、form中将token放在隐藏域中,保存时将token放头部一起提交

3、获取头部token,与session中的token比较,一致则通过,否则不予提交

4、生成新的token,并传给前端

1、拦截器配置

1
2
3
4
5
6
7
8
9
10
<mvc:interceptors>
    <!-- csrf攻击防御 -->
    <mvc:interceptor>
        <!-- 需拦截的地址 -->
        <mvc:mapping path="/**" />
        <!-- 需排除拦截的地址 -->
        <mvc:exclude-mapping path="/resources/**" />
        <bean class="com.cnpc.framework.interceptor.CSRFInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

2拦截器实现 CSRFInterceptor.java

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
package com.cnpc.framework.interceptor;
 
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
import com.alibaba.fastjson.JSONObject;
import com.cnpc.framework.annotation.RefreshCSRFToken;
import com.cnpc.framework.annotation.VerifyCSRFToken;
import com.cnpc.framework.base.pojo.ResultCode;
import com.cnpc.framework.constant.CodeConstant;
import com.cnpc.framework.utils.CSRFTokenUtil;
import com.cnpc.framework.utils.StrUtil;
 
/**
 * CSRFInterceptor 防止跨站请求伪造拦截器
 *
 * @author billJiang 2016年10月6日 下午8:14:40
 */
public class CSRFInterceptor extends HandlerInterceptorAdapter {
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 
        System.out.println("---------->" + request.getRequestURI());
        System.out.println(request.getHeader("X-Requested-With"));
        // 提交表单token 校验
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        VerifyCSRFToken verifyCSRFToken = method.getAnnotation(VerifyCSRFToken.class);
        // 如果配置了校验csrf token校验,则校验
        if (verifyCSRFToken != null) {
            // 是否为Ajax标志
            String xrq = request.getHeader("X-Requested-With");
            // 非法的跨站请求校验
            if (verifyCSRFToken.verify() && !verifyCSRFToken(request)) {
                if (StrUtil.isEmpty(xrq)) {
                    // form表单提交,url get方式,刷新csrftoken并跳转提示页面
                    String csrftoken = CSRFTokenUtil.generate(request);
                    request.getSession().setAttribute("CSRFToken", csrftoken);
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print("非法请求");
                    response.flushBuffer();
                    return false;
                } else {
                    // 刷新CSRFToken,返回错误码,用于ajax处理,可自定义
                    String csrftoken = CSRFTokenUtil.generate(request);
                    request.getSession().setAttribute("CSRFToken", csrftoken);
                    ResultCode rc = CodeConstant.CSRF_ERROR;
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print(JSONObject.toJSONString(rc));
                    response.flushBuffer();
                    return false;
                }
            }
 
        }
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
 
        // 第一次生成token
        if (modelAndView != null) {
            if (request.getSession(false) == null || StrUtil.isEmpty((String) request.getSession(false).getAttribute("CSRFToken"))) {
                request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
                return;
            }
        }
        // 刷新token
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);
 
        // 跳转到一个新页面 刷新token
        String xrq = request.getHeader("X-Requested-With");
        if (refreshAnnotation != null && refreshAnnotation.refresh() && StrUtil.isEmpty(xrq)) {
            request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
            return;
        }
 
        // 校验成功 刷新token 可以防止重复提交
        VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);
        if (verifyAnnotation != null) {
            if (verifyAnnotation.verify()) {
                if (StrUtil.isEmpty(xrq)) {
                    request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
                } else {
                    Map<String, String> map = new HashMap<String, String>();
                    map.put("CSRFToken", CSRFTokenUtil.generate(request));
                    response.setContentType("application/json;charset=UTF-8");
                    OutputStream out = response.getOutputStream();
                    out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8"));
                }
            }
        }
    }
 
    /**
     * 处理跨站请求伪造 针对需要登录后才能处理的请求,验证CSRFToken校验
     *
     * @param request
     */
    protected boolean verifyCSRFToken(HttpServletRequest request) {
 
        // 请求中的CSRFToken
        String requstCSRFToken = request.getHeader("CSRFToken");
        if (StrUtil.isEmpty(requstCSRFToken)) {
            return false;
        }
        String sessionCSRFToken = (String) request.getSession().getAttribute("CSRFToken");
        if (StrUtil.isEmpty(sessionCSRFToken)) {
            return false;
        }
        return requstCSRFToken.equals(sessionCSRFToken);
    }
}

 3.CSRFTokenUtil.java 生成随机系列号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.cnpc.framework.utils;
 
import java.util.UUID;
 
import javax.servlet.http.HttpServletRequest;
 
public class CSRFTokenUtil {
 
    public static String generate(HttpServletRequest request) {
 
        return UUID.randomUUID().toString();
    }
 
}

  4、ResultCode

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
package com.cnpc.framework.base.pojo;
 
public class ResultCode {
 
    private String code;
 
    private String message;
 
    public ResultCode(String code, String message) {
 
        this.code = code;
        this.message = message;
    }
 
    public String getCode() {
 
        return code;
    }
 
    public void setCode(String code) {
 
        this.code = code;
    }
 
    public String getMessage() {
 
        return message;
    }
 
    public void setMessage(String message) {
 
        this.message = message;
    }
 
}

  5、CodeConstant.java

1
2
3
4
5
6
7
8
package com.cnpc.framework.constant;
 
import com.cnpc.framework.base.pojo.ResultCode;
 
public class CodeConstant {
 
    public final static ResultCode CSRF_ERROR = new ResultCode("101", "CSRF ERROR:无效的token,或者token过期");
}

  6、ajax提交方法

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
function ajaxPost(url, params, callback) {
    var result = null;
    var headers={};
    headers['CSRFToken']=$("#csrftoken").val();
     
    $.ajax({
        type : 'post',
        async : false,
        url : url,
        data : params,
        dataType : 'json',
        headers:headers,
        success : function(data, status) {
            result = data;
            if(data&&data.code&&data.code=='101'){
                modals.error("操作失败,请刷新重试,具体错误:"+data.message);
                return false;
            }
            if (callback) {
                callback.call(this, data, status);
            }
        },
        error : function(err, err1, err2) {
            if(err && err.readyState && err.readyState == '4'){
                var responseBody = err.responseText;
                if(responseBody){  
                     responseBody = "{'retData':"+responseBody;
                     var resJson = eval('(' + responseBody + ')');
                     $("#csrftoken").val(resJson.csrf.CSRFToken);
                     this.success(resJson.retData, 200);
                }
                return ;
            }          
            modals.error({
                text : JSON.stringify(err) + '<br/>err1:' + JSON.stringify(err1) + '<br/>err2:' + JSON.stringify(err2),
                large : true
            });
        }
    });
 
    return result;
}

  7、form表单配置

1
<input type='hidden' value='${CSRFToken}' id='csrftoken'>

  8、注解定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.cnpc.framework.annotation;
 
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * 跨站请求仿照注解 刷新CSRFToken
 *
 */
@Target({ java.lang.annotation.ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RefreshCSRFToken {
 
    /**
     * 刷新token
     *
     * @return
     */
    public abstract boolean refresh() default true;
}

  9、注解配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RefreshCSRFToken
 @RequestMapping(method = RequestMethod.GET, value = "/edit")
 private String edit(String id, HttpServletRequest request) {
 
     request.setAttribute("id", id);
     return "base/user/user_edit";
 }
 
 @RequestMapping(method = RequestMethod.POST, value = "/get")
 @ResponseBody
 private User getUser(String id) {
 
     return userService.get(User.class, id);
 }

  

posted on   为莹  阅读(4531)  评论(0编辑  收藏  举报

< 2025年2月 >
26 27 28 29 30 31 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 1
2 3 4 5 6 7 8

统计

点击右上角即可分享
微信分享提示