Spring mvc拦截器防御CSRF攻击

本文介绍了一种有效的CSRF(跨站请求伪造)防护机制,包括生成随机token并存于session,将token放入form隐藏域及请求头进行校验,以及在拦截器中实现token的验证与刷新流程。通过注解和自定义工具类,该机制能有效防止非法请求。

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. <mvc:interceptors>

  2. <!-- csrf攻击防御 -->

  3. <mvc:interceptor>

  4. <!-- 需拦截的地址 -->

  5. <mvc:mapping path="/**" />

  6. <!-- 需排除拦截的地址 -->

  7. <mvc:exclude-mapping path="/resources/**" />

  8. <bean class="com.cnpc.framework.interceptor.CSRFInterceptor" />

  9. </mvc:interceptor>

  10. </mvc:interceptors>

2、拦截器实现 CSRFInterceptor.java

 

 
  1. package com.cnpc.framework.interceptor;

  2.  
  3. import java.io.OutputStream;

  4. import java.io.PrintWriter;

  5. import java.lang.reflect.Method;

  6. import java.util.HashMap;

  7. import java.util.Map;

  8.  
  9. import javax.servlet.http.HttpServletRequest;

  10. import javax.servlet.http.HttpServletResponse;

  11.  
  12. import org.springframework.web.method.HandlerMethod;

  13. import org.springframework.web.servlet.ModelAndView;

  14. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

  15.  
  16. import com.alibaba.fastjson.JSONObject;

  17. import com.cnpc.framework.annotation.RefreshCSRFToken;

  18. import com.cnpc.framework.annotation.VerifyCSRFToken;

  19. import com.cnpc.framework.base.pojo.ResultCode;

  20. import com.cnpc.framework.constant.CodeConstant;

  21. import com.cnpc.framework.utils.CSRFTokenUtil;

  22. import com.cnpc.framework.utils.StrUtil;

  23.  
  24. /**

  25. * CSRFInterceptor 防止跨站请求伪造拦截器

  26. *

  27. * @author billJiang 2016年10月6日 下午8:14:40

  28. */

  29. public class CSRFInterceptor extends HandlerInterceptorAdapter {

  30.  
  31. @Override

  32. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  33.  
  34. System.out.println("---------->" + request.getRequestURI());

  35. System.out.println(request.getHeader("X-Requested-With"));

  36. // 提交表单token 校验

  37. HandlerMethod handlerMethod = (HandlerMethod) handler;

  38. Method method = handlerMethod.getMethod();

  39. VerifyCSRFToken verifyCSRFToken = method.getAnnotation(VerifyCSRFToken.class);

  40. // 如果配置了校验csrf token校验,则校验

  41. if (verifyCSRFToken != null) {

  42. // 是否为Ajax标志

  43. String xrq = request.getHeader("X-Requested-With");

  44. // 非法的跨站请求校验

  45. if (verifyCSRFToken.verify() && !verifyCSRFToken(request)) {

  46. if (StrUtil.isEmpty(xrq)) {

  47. // form表单提交,url get方式,刷新csrftoken并跳转提示页面

  48. String csrftoken = CSRFTokenUtil.generate(request);

  49. request.getSession().setAttribute("CSRFToken", csrftoken);

  50. response.setContentType("application/json;charset=UTF-8");

  51. PrintWriter out = response.getWriter();

  52. out.print("非法请求");

  53. response.flushBuffer();

  54. return false;

  55. } else {

  56. // 刷新CSRFToken,返回错误码,用于ajax处理,可自定义

  57. String csrftoken = CSRFTokenUtil.generate(request);

  58. request.getSession().setAttribute("CSRFToken", csrftoken);

  59. ResultCode rc = CodeConstant.CSRF_ERROR;

  60. response.setContentType("application/json;charset=UTF-8");

  61. PrintWriter out = response.getWriter();

  62. out.print(JSONObject.toJSONString(rc));

  63. response.flushBuffer();

  64. return false;

  65. }

  66. }

  67.  
  68. }

  69. return true;

  70. }

  71.  
  72. @Override

  73. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

  74. throws Exception {

  75.  
  76. // 第一次生成token

  77. if (modelAndView != null) {

  78. if (request.getSession(false) == null || StrUtil.isEmpty((String) request.getSession(false).getAttribute("CSRFToken"))) {

  79. request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));

  80. return;

  81. }

  82. }

  83. // 刷新token

  84. HandlerMethod handlerMethod = (HandlerMethod) handler;

  85. Method method = handlerMethod.getMethod();

  86. RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);

  87.  
  88. // 跳转到一个新页面 刷新token

  89. String xrq = request.getHeader("X-Requested-With");

  90. if (refreshAnnotation != null && refreshAnnotation.refresh() && StrUtil.isEmpty(xrq)) {

  91. request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));

  92. return;

  93. }

  94.  
  95. // 校验成功 刷新token 可以防止重复提交

  96. VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);

  97. if (verifyAnnotation != null) {

  98. if (verifyAnnotation.verify()) {

  99. if (StrUtil.isEmpty(xrq)) {

  100. request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));

  101. } else {

  102. Map<String, String> map = new HashMap<String, String>();

  103. map.put("CSRFToken", CSRFTokenUtil.generate(request));

  104. response.setContentType("application/json;charset=UTF-8");

  105. OutputStream out = response.getOutputStream();

  106. out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8"));

  107. }

  108. }

  109. }

  110. }

  111.  
  112. /**

  113. * 处理跨站请求伪造 针对需要登录后才能处理的请求,验证CSRFToken校验

  114. *

  115. * @param request

  116. */

  117. protected boolean verifyCSRFToken(HttpServletRequest request) {

  118.  
  119. // 请求中的CSRFToken

  120. String requstCSRFToken = request.getHeader("CSRFToken");

  121. if (StrUtil.isEmpty(requstCSRFToken)) {

  122. return false;

  123. }

  124. String sessionCSRFToken = (String) request.getSession().getAttribute("CSRFToken");

  125. if (StrUtil.isEmpty(sessionCSRFToken)) {

  126. return false;

  127. }

  128. return requstCSRFToken.equals(sessionCSRFToken);

  129. }

  130. }


3、CSRFTokenUtil.java

 

 
  1. package com.cnpc.framework.utils;

  2.  
  3. import java.util.UUID;

  4.  
  5. import javax.servlet.http.HttpServletRequest;

  6.  
  7. public class CSRFTokenUtil {

  8.  
  9. public static String generate(HttpServletRequest request) {

  10.  
  11. return UUID.randomUUID().toString();

  12. }

  13.  
  14. }

4、ResultCode

 

 
  1. package com.cnpc.framework.base.pojo;

  2.  
  3. public class ResultCode {

  4.  
  5. private String code;

  6.  
  7. private String message;

  8.  
  9. public ResultCode(String code, String message) {

  10.  
  11. this.code = code;

  12. this.message = message;

  13. }

  14.  
  15. public String getCode() {

  16.  
  17. return code;

  18. }

  19.  
  20. public void setCode(String code) {

  21.  
  22. this.code = code;

  23. }

  24.  
  25. public String getMessage() {

  26.  
  27. return message;

  28. }

  29.  
  30. public void setMessage(String message) {

  31.  
  32. this.message = message;

  33. }

  34.  
  35. }

 

5、CodeConstant.java

 

 
  1. package com.cnpc.framework.constant;

  2.  
  3. import com.cnpc.framework.base.pojo.ResultCode;

  4.  
  5. public class CodeConstant {

  6.  
  7. public final static ResultCode CSRF_ERROR = new ResultCode("101", "CSRF ERROR:无效的token,或者token过期");

  8. }

 

6、ajax提交方法

 

 
  1. function ajaxPost(url, params, callback) {

  2. var result = null;

  3. var headers={};

  4. headers['CSRFToken']=$("#csrftoken").val();

  5.  
  6. $.ajax({

  7. type : 'post',

  8. async : false,

  9. url : url,

  10. data : params,

  11. dataType : 'json',

  12. headers:headers,

  13. success : function(data, status) {

  14. result = data;

  15. if(data&&data.code&&data.code=='101'){

  16. modals.error("操作失败,请刷新重试,具体错误:"+data.message);

  17. return false;

  18. }

  19. if (callback) {

  20. callback.call(this, data, status);

  21. }

  22. },

  23. error : function(err, err1, err2) {

  24. if(err && err.readyState && err.readyState == '4'){

  25. var responseBody = err.responseText;

  26. if(responseBody){

  27. responseBody = "{'retData':"+responseBody;

  28. var resJson = eval('(' + responseBody + ')');

  29. $("#csrftoken").val(resJson.csrf.CSRFToken);

  30. this.success(resJson.retData, 200);

  31. }

  32. return ;

  33. }

  34. modals.error({

  35. text : JSON.stringify(err) + '<br/>err1:' + JSON.stringify(err1) + '<br/>err2:' + JSON.stringify(err2),

  36. large : true

  37. });

  38. }

  39. });

  40.  
  41. return result;

  42. }

 

7、form表单配置

 

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

 

8、注解定义

 

 
  1. package com.cnpc.framework.annotation;

  2.  
  3. import java.lang.annotation.Retention;

  4. import java.lang.annotation.RetentionPolicy;

  5. import java.lang.annotation.Target;

  6.  
  7. /**

  8. * 跨站请求仿照注解 刷新CSRFToken

  9. *

  10. */

  11. @Target({ java.lang.annotation.ElementType.METHOD })

  12. @Retention(RetentionPolicy.RUNTIME)

  13. public @interface RefreshCSRFToken {

  14.  
  15. /**

  16. * 刷新token

  17. *

  18. * @return

  19. */

  20. public abstract boolean refresh() default true;

  21. }

 

 
  1. package com.cnpc.framework.annotation;

  2.  
  3. import java.lang.annotation.Retention;

  4. import java.lang.annotation.RetentionPolicy;

  5. import java.lang.annotation.Target;

  6.  
  7. /**

  8. * 跨站请求仿照注解

  9. */

  10. @Target({ java.lang.annotation.ElementType.METHOD })

  11. @Retention(RetentionPolicy.RUNTIME)

  12. public @interface VerifyCSRFToken {

  13.  
  14. /**

  15. * 需要验证防跨站请求

  16. *

  17. */

  18. public abstract boolean verify() default true;

  19.  
  20. }


9、注解配置

 

 
  1. @RefreshCSRFToken

  2. @RequestMapping(method = RequestMethod.GET, value = "/edit")

  3. private String edit(String id, HttpServletRequest request) {

  4.  
  5. request.setAttribute("id", id);

  6. return "base/user/user_edit";

  7. }

  8.  
  9. @RequestMapping(method = RequestMethod.POST, value = "/get")

  10. @ResponseBody

  11. private User getUser(String id) {

  12.  
  13. return userService.get(User.class, id);

  14. }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值