spring mvc拦截POST请求防CSRF攻击

[1].[代码] CsrfTokenManager 用于管理csrfToken相关 跳至 [1] [2] [3] [4] [5] [6] [7]

?
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
package com.uncle5.pubrub.web.common;
 
import java.util.UUID;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
public final class CsrfTokenManager {
 
     // 隐藏域参数名称
     static final String CSRF_PARAM_NAME = "CSRFToken" ;
 
     // session中csrfToken参数名称
     public static final String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CsrfTokenManager. class
             .getName() + ".tokenval" ;
 
     private CsrfTokenManager() {
     };
 
     // 在session中创建csrfToken
     public static String createTokenForSession(HttpSession session) {
         String token = null ;
 
         synchronized (session) {
             token = (String) session
                     .getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);
             if ( null == token) {
                 token = UUID.randomUUID().toString();
                 session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME, token);
             }
         }
         return token;
     }
 
     public static String getTokenFromRequest(HttpServletRequest request) {
         return request.getParameter(CSRF_PARAM_NAME);
     }
}

[2].[代码] CsrfRequestDataValueProcessor 自动创建hidden的csrfToken域的类 跳至 [1] [2] [3] [4] [5] [6] [7]

?
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
package com.uncle5.pubrub.web.common;
 
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
 
import com.google.common.collect.Maps;
 
@Component ( "requestDataValueProcessor" )
public class CsrfRequestDataValueProcessor implements RequestDataValueProcessor {
 
     @Override
     public String processAction(HttpServletRequest request, String action) {
         // TODO 暂时原样返回action
         return action;
     }
 
     @Override
     public String processFormFieldValue(HttpServletRequest request,
             String name, String value, String type) {
         // TODO 暂时原样返回value
         return value;
     }
 
     @Override
     public Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
         //此处是当使用spring的taglib标签<form:form>创建表单时候,增加的隐藏域参数
         Map<String, String> hiddenFields = Maps.newHashMap();
         hiddenFields.put(CsrfTokenManager.CSRF_PARAM_NAME,
                 CsrfTokenManager.createTokenForSession(request.getSession()));
 
         return hiddenFields;
     }
 
     @Override
     public String processUrl(HttpServletRequest request, String url) {
         // TODO 暂时原样返回url
         return url;
     }
 
}

[3].[代码] CsrfInterceptor 对于post请求进行拦截,检测csrfToken是否匹配 跳至 [1] [2] [3] [4] [5] [6] [7]

?
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
package com.uncle5.pubrub.web.security;
 
import java.net.URLEncoder;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
import com.uncle5.pubrub.web.common.CsrfTokenManager;
import com.uncle5.pubrub.web.common.WebUser;
 
public class CsrfInterceptor extends HandlerInterceptorAdapter {
 
     private static final Logger logger = LoggerFactory
             .getLogger(CsrfInterceptor. class );
 
     @Autowired
     WebUser webUser;
 
     @Override
     public boolean preHandle(HttpServletRequest request,
             HttpServletResponse response, Object handler) throws Exception {
 
         if ( "POST" .equalsIgnoreCase(request.getMethod())) {
             String CsrfToken = CsrfTokenManager.getTokenFromRequest(request);
             if (CsrfToken == null
                     || !CsrfToken.equals(request.getSession().getAttribute(
                             CsrfTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME))) {
                 String reLoginUrl = "/login?backurl="
                         + URLEncoder.encode(getCurrentUrl(request), "utf-8" );
 
                 response.sendRedirect(reLoginUrl);
                 return false ;
             }
         }
 
         return true ;
     }
 
     private String getCurrentUrl(HttpServletRequest request) {
         String currentUrl = request.getRequestURL().toString();
         if (!StringUtils.isEmpty(request.getQueryString())) {
             currentUrl += "?" + request.getQueryString();
         }
 
         return currentUrl;
     }
}

[4].[代码] springMVC 配置文件,增加需要进行拦截的url 跳至 [1] [2] [3] [4] [5] [6] [7]

?
1
2
3
4
5
6
7
8
<!-- 安全拦截用的 -->
     < mvc:interceptors >
         < mvc:interceptor >
             < mvc:mapping path = "/forum/post" />
             < mvc:mapping path = "/forum/reply/*" />
             < bean class = "com.uncle5.pubrub.web.security.CsrfInterceptor" />
         </ mvc:interceptor >       
     </ mvc:interceptors >

[5].[代码] jsp页面,需要注意的是必须使用spring的form标签 跳至 [1] [2] [3] [4] [5] [6] [7]

?
1
2
3
4
5
6
7
8
9
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
 
< div style = "margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;" >
         < form:form action = "/forum/post" method = "post" >
             < span >标题:</ span >< input type = "text" name = "title" id = "title" >
             < textarea name = "content" id = "content" rows = "30" cols = "40" ></ textarea >
             < input type = "submit" name = "submit" value = "submit" >
         </ form:form >
     </ div >

[6].[代码] jsp页面 查看源码会发现已经添加上了hidden字段 跳至 [1] [2] [3] [4] [5] [6] [7]

?
1
2
3
4
5
6
7
8
< div style = "margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;" >
         < form id = "command" action = "/forum/post" method = "post" >
             < span >标题:</ span >< input type = "text" name = "title" id = "title" >
             < textarea name = "content" id = "content" rows = "30" cols = "40" ></ textarea >          
             < input type = "submit" name = "submit" value = "submit" >
         < input type = "hidden" name = "CSRFToken" value = "35afec82-da7b-449e-9ae9-b38664b5af63" />
</ form >
     </ div >

[7].[代码] 相关原理说明 跳至 [1] [2] [3] [4] [5] [6] [7]

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 .只有当使用spring的form标签时候,才会在渲染jsp页面时候触发
org.springframework.web.servlet.tags.form.FormTag 类中的
 
@Override
     public int doEndTag() throws JspException {
         RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor();
         ServletRequest request = this .pageContext.getRequest();
         if ((processor != null ) && (request instanceof HttpServletRequest)) {
             writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request));
         }
         this .tagWriter.endTag();
         return EVAL_PAGE;
     }
该方法会调用上文中所说的CsrfRequestDataValueProcessor中的创建隐藏域的方法getExtraHiddenFields来创建csrfToken隐藏域。
 
2 .对相关需要进行防范csrf攻击的post请求地址进行拦截,此处是依赖springMVC中提供的拦截器机制来操作的
<mvc:interceptors>
         <mvc:interceptor>
             <mvc:mapping path= "/forum/post" />
             <mvc:mapping path= "/forum/reply/*" />
             <bean class = "com.uncle5.pubrub.web.security.CsrfInterceptor" />
         </mvc:interceptor>
     </mvc:interceptors>
当用户访问 "/forum/post" 这个请求时候,会直接进入CsrfInterceptor的preHandle方法,此处针对post请求进行了判断,如果从request中获取的csrfToken不存在或者与session中的csrfToken不相匹配,那么可以不进行后续流程,至于返回 404 还是返回到登录页面,就随便作者了。

你可能感兴趣的:(spring mvc拦截POST请求防CSRF攻击)