您好,登錄后才能下訂單哦!
這篇文章給大家介紹SpringSecurity系列之請求默認已開啟防火墻的示例分析,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
自己寫當然也可以實現,但是大部分情況下,大家都不是專業的 Web 安全工程師,所以考慮問題也不過就是認證和授權,這兩個問題處理好了,似乎系統就很安全了。
其實不是這樣的!
各種各樣的 Web 攻擊每天都在發生,什么固定會話攻擊、csrf 攻擊等等,如果不了解這些攻擊,那么做出來的系統肯定也不能防御這些攻擊。
使用 Spring Security 的好處就是,即使不了解這些攻擊,也不用擔心這些攻擊,因為 Spring Security 已經幫你做好防御工作了。
我們常說相比于 Shiro,Spring Security 更加重量級,重量級有重量級的好處,比如功能全,安全管理更加完備。用了 Spring Security,你都不知道自己的系統有多安全!
今天我就來和大家聊一聊 Spring Security 中自帶的防火墻機制。
好了,不廢話了,我們來看文章。
在 Spring Security 中提供了一個 HttpFirewall,看名字就知道這是一個請求防火墻,它可以自動處理掉一些非法請求。
HttpFirewall 目前一共有兩個實現類:
一個是嚴格模式的防火墻設置,還有一個默認防火墻設置。
DefaultHttpFirewall 的限制相對于 StrictHttpFirewall 要寬松一些,當然也意味著安全性不如 StrictHttpFirewall。
Spring Security 中默認使用的是 StrictHttpFirewall。
那么 StrictHttpFirewall 都是從哪些方面來保護我們的應用呢?我們來挨個看下。
首先,對于請求的方法,只允許白名單中的方法,也就是說,不是所有的 HTTP 請求方法都可以執行。
這點我們可以從 StrictHttpFirewall 的源碼中看出來:
public class StrictHttpFirewall implements HttpFirewall { private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods(); private static Set<String> createDefaultAllowedHttpMethods() { Set<String> result = new HashSet<>(); result.add(HttpMethod.DELETE.name()); result.add(HttpMethod.GET.name()); result.add(HttpMethod.HEAD.name()); result.add(HttpMethod.OPTIONS.name()); result.add(HttpMethod.PATCH.name()); result.add(HttpMethod.POST.name()); result.add(HttpMethod.PUT.name()); return result; } private void rejectForbiddenHttpMethod(HttpServletRequest request) { if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { return; } if (!this.allowedHttpMethods.contains(request.getMethod())) { throw new RequestRejectedException("The request was rejected because the HTTP method \"" + request.getMethod() + "\" was not included within the whitelist " + this.allowedHttpMethods); } } }
從這段代碼中我們看出來,你的 HTTP 請求方法必須是 DELETE、GET、HEAD、OPTIONS、PATCH、POST 以及 PUT 中的一個,請求才能發送成功,否則的話,就會拋出 RequestRejectedException 異常。
那如果你想發送其他 HTTP 請求方法,例如 TRACE ,該怎么辦呢?我們只需要自己重新提供一個 StrictHttpFirewall 實例即可,如下:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setUnsafeAllowAnyHttpMethod(true); return firewall; }
其中,setUnsafeAllowAnyHttpMethod 方法表示不做 HTTP 請求方法校驗,也就是什么方法都可以過。或者也可以通過 setAllowedHttpMethods 方法來重新定義可以通過的方法。
不知掉大家有沒有試過,如果你使用了 Spring Security,請求地址是不能有 ; 的,如果請求地址有 ; ,就會自動跳轉到如下頁面:
可以看到,頁面的提示中已經說了,因為你的請求地址中包含 ;,所以請求失敗。
什么時候請求地址中會包含 ; 呢?不知道小伙伴們在使用 Shiro 的時候,有沒有注意到,如果你禁用了 Cookie,那么 jsessionid 就會出現在地址欄里,像下面這樣:
http://localhost:8080/hello;jsessionid=xx
這種傳遞 jsessionid 的方式實際上是非常不安全的(松哥后面的文章會和大家細聊這個問題),所以在 Spring Security 中,這種傳參方式默認就禁用了。
當然,如果你希望地址欄能夠被允許出現 ; ,那么可以按照如下方式設置:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowSemicolon(true); return firewall; }
設置完成之后,再去訪問相同的接口,可以看到,此時雖然還是報錯,但是錯誤是 404 了,而不是一開始那個不允許 ; 的錯了。
注意,在 URL 地址中,; 編碼之后是 %3b 或者 %3B,所以地址中同樣不能出現 %3b 或者 %3B
題外話
有的小伙伴可能不知道或者沒用過,Spring3.2 開始,帶來了一種全新的傳參方式 @MatrixVariable。
@MatrixVariable 是 Spring3.2 中帶來的功能,這種方式拓展了請求參數的傳遞格式,使得參數之間可以用 ; 隔開,這種傳參方式真是哪壺不開提哪壺。因為 Spring Security 默認就是禁止這種傳參方式,所以一般情況下,如果你需要使用 @MatrixVariable 來標記參數,就得在 Spring Security 中額外放行。
接下來我通過一個簡單的例子來和大家演示一下 @MatrixVariable 的用法。
我們新建一個 /hello 方法:
@RequestMapping(value = "/hello/{id}") public void hello(@PathVariable Integer id,@MatrixVariable String name) { System.out.println("id = " + id); System.out.println("name = " + name); }
另外我們還需要配置一下 SpringMVC,使 ; 不要被自動移除了:
@Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } }
然后啟動項目(注意,Spring Security 中也已經配置了允許 URL 中存在 ;),瀏覽器發送如下請求:
http://localhost:8080/hello/123;name=javaboy
控制臺打印信息如下:
id = 123 name = javaboy
可以看到,@MatrixVariable 注解已經生效了。
請求地址必須是標準化 URL。
什么是標準化 URL?標準化 URL 主要從四個方面來判斷,我們來看下源碼:
StrictHttpFirewall#isNormalized:
private static boolean isNormalized(HttpServletRequest request) { if (!isNormalized(request.getRequestURI())) { return false; } if (!isNormalized(request.getContextPath())) { return false; } if (!isNormalized(request.getServletPath())) { return false; } if (!isNormalized(request.getPathInfo())) { return false; } return true; }
getRequestURI 就是獲取請求協議之外的字符;getContextPath 是獲取上下文路徑,相當于是 project 的名字;getServletPath 這個就是請求的 servlet 路徑,getPathInfo 則是除過 contextPath 和 servletPath 之后剩余的部分。
這四種路徑中,都不能包含如下字符串:
"./", "/../" or "/."
如果請求地址中包含不可打印的 ASCII 字符,請求則會被拒絕,我們可以從源碼中看出端倪:
StrictHttpFirewall#containsOnlyPrintableAsciiCharacters
private static boolean containsOnlyPrintableAsciiCharacters(String uri) { int length = uri.length(); for (int i = 0; i < length; i++) { char c = uri.charAt(i); if (c < '\u0020' || c > '\u007e') { return false; } } return true; }
如果請求地址中出現雙斜杠,這個請求也將被拒絕。雙斜杠 // 使用 URL 地址編碼之后,是 %2F%2F,其中 F 大小寫無所謂,所以請求地址中也能不出現 "%2f%2f", "%2f%2F", "%2F%2f", "%2F%2F"。
如果你希望請求地址中可以出現 // ,可以按照如下方式配置:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedDoubleSlash(true); return firewall; }
如果請求地址中出現 %,這個請求也將被拒絕。URL 編碼后的 % 是 %25,所以 %25 也不能出現在 URL 地址中。
如果希望請求地址中可以出現 %,可以按照如下方式修改:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedPercent(true); return firewall; }
如果請求地址中包含斜杠編碼后的字符 %2F 或者 %2f ,則請求將被拒絕。
如果請求地址中包含反斜杠 \ 或者反斜杠編碼后的字符 %5C 或者 %5c ,則請求將被拒絕。
如果希望去掉如上兩條限制,可以按照如下方式來配置:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowBackSlash(true); firewall.setAllowUrlEncodedSlash(true); return firewall; }
如果請求地址中存在 . 編碼之后的字符 %2e、%2E,則請求將被拒絕。
如需支持,按照如下方式進行配置:
@Bean HttpFirewall httpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedPeriod(true); return firewall; }
需要強調一點,上面所說的這些限制,都是針對請求的 requestURI 進行的限制,而不是針對請求參數。例如你的請求格式是:
http://localhost:8080/hello?param=aa%2ebb
那么 2.7 小節說的限制和你沒關系。
這個大家從 StrictHttpFirewall 源碼中很容易看到:
public class StrictHttpFirewall implements HttpFirewall { @Override public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { rejectForbiddenHttpMethod(request); rejectedBlacklistedUrls(request); rejectedUntrustedHosts(request); if (!isNormalized(request)) { throw new RequestRejectedException("The request was rejected because the URL was not normalized."); } String requestUri = request.getRequestURI(); if (!containsOnlyPrintableAsciiCharacters(requestUri)) { throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters."); } return new FirewalledRequest(request) { @Override public void reset() { } }; } private void rejectedBlacklistedUrls(HttpServletRequest request) { for (String forbidden : this.encodedUrlBlacklist) { if (encodedUrlContains(request, forbidden)) { throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); } } for (String forbidden : this.decodedUrlBlacklist) { if (decodedUrlContains(request, forbidden)) { throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); } } } private static boolean encodedUrlContains(HttpServletRequest request, String value) { if (valueContains(request.getContextPath(), value)) { return true; } return valueContains(request.getRequestURI(), value); } private static boolean decodedUrlContains(HttpServletRequest request, String value) { if (valueContains(request.getServletPath(), value)) { return true; } if (valueContains(request.getPathInfo(), value)) { return true; } return false; } private static boolean valueContains(String value, String contains) { return value != null && value.contains(contains); } }
rejectedBlacklistedUrls 方法就是校驗 URL 的,該方法邏輯很簡單,我就不再贅述了。
關于SpringSecurity系列之請求默認已開啟防火墻的示例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。