1. 程式人生 > >Spring Security筆記:解決CsrfFilter與Rest服務Post方式的矛盾

Spring Security筆記:解決CsrfFilter與Rest服務Post方式的矛盾

基於Spring Security+Spring MVC的web應用,為了防止跨站提交攻擊,通常會配置csrf,即:

1     <http ...>
2         ...
3         <csrf />        
4     </http>

如果應用中有Post方式訪問的Rest服務(參考下面的程式碼),會很不幸的發現,所有POST方式請求的服務會呼叫失敗。

複製程式碼
1     @RequestMapping(value = "/user/create", method = RequestMethod.POST)
2     @ResponseBody
3 public UserInfo createUser(@RequestBody(required = true) UserInfo user, 4 HttpServletRequest request, HttpServletResponse response) 5 throws Exception { 6 ... 7 }
複製程式碼

原因在於:啟用csrf後,所有http請求都被會CsrfFilter攔截,而CsrfFilter中有一個私有類DefaultRequiresCsrfMatcher

複製程式碼
 1     private
static final class DefaultRequiresCsrfMatcher implements RequestMatcher { 2 private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); 3 4 /* (non-Javadoc) 5 * @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
6 */ 7 public boolean matches(HttpServletRequest request) { 8 return !allowedMethods.matcher(request.getMethod()).matches(); 9 } 10 }
複製程式碼

從這段原始碼可以發現,POST方法被排除在外了,也就是說只有GET|HEAD|TRACE|OPTIONS這4類方法會被放行,其它Method的http請求,都要驗證_csrf的token是否正確,而通常post方式呼叫rest服務時,又沒有_csrf的token,所以校驗失敗。

解決方法:自己弄一個Matcher

複製程式碼
 1 package com.cnblogs.yjmyzz.utils;
 2 
 3 import java.util.List;
 4 import java.util.regex.Pattern;
 5 
 6 import javax.servlet.http.HttpServletRequest;
 7 
 8 import org.springframework.security.web.util.matcher.RequestMatcher;
 9 
10 public class CsrfSecurityRequestMatcher implements RequestMatcher {
11     private Pattern allowedMethods = Pattern
12             .compile("^(GET|HEAD|TRACE|OPTIONS)$");
13 
14     public boolean matches(HttpServletRequest request) {
15 
16         if (execludeUrls != null && execludeUrls.size() > 0) {
17             String servletPath = request.getServletPath();
18             for (String url : execludeUrls) {
19                 if (servletPath.contains(url)) {
20                     return false;
21                 }
22             }
23         }
24         return !allowedMethods.matcher(request.getMethod()).matches();
25     }
26 
27     /**
28      * 需要排除的url列表
29      */
30     private List<String> execludeUrls;
31 
32     public List<String> getExecludeUrls() {
33         return execludeUrls;
34     }
35 
36     public void setExecludeUrls(List<String> execludeUrls) {
37         this.execludeUrls = execludeUrls;
38     }
39 }
複製程式碼

這裡添加了一個屬性execludeUrls,允許人為排除哪些url。

然後在配置檔案裡,這樣修改:

複製程式碼
 1     <http entry-point-ref="loginEntryPoint" use-expressions="true">
 2         ...
 3         <intercept-url pattern="/rest/**" access="permitAll" />
 4         ...
 5         <csrf request-matcher-ref="csrfSecurityRequestMatcher"/>        
 6     </http>
 7     
 8     <beans:bean id="csrfSecurityRequestMatcher" class="com.cnblogs.yjmyzz.utils.CsrfSecurityRequestMatcher">
 9         <beans:property name="execludeUrls">
10             <beans:list>
11                 <beans:value>/rest/</beans:value>
12             </beans:list>
13         </beans:property>
14     </beans:bean>
複製程式碼

這裡約定所有/rest/開頭的都是Rest服務地址,上面的配置就把/rest/排除在csrf驗證的範圍之外了.