今天有個接口打算使用矩陣變量來綁定參數,即使用@MatrixVariable注解來接收參數
調用接口后項目報了如下錯誤
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
完成的異常棧軌跡如下
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";" at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:369) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:336) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:194) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) ~[spring-boot-actuator-2.2.2.RELEASE.jar:2.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.SessionRestoringHandler.handleRequest(SessionRestoringHandler.java:119) ~[undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) [undertow-servlet-2.0.28.Final.jar:2.0.28.Final] at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) [undertow-core-2.0.28.Final.jar:2.0.28.Final] at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) [undertow-core-2.0.28.Final.jar:2.0.28.Final] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_181] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_181] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_181]
解決方法
前提,springmvc默認不能用矩陣變量,所以記得開啟,springboot可以用如下方式開啟
@Configuration public class WebMvcAuthConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper=new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); } }
然后如果出現了標題的異常可以加入如下配置即可解決
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { StrictHttpFirewall firewall = new StrictHttpFirewall(); //去掉";"黑名單 firewall.setAllowSemicolon(true); //加入自定義的防火牆 web.httpFirewall(firewall); super.configure(web); } }
閑的同學可以看下解決思路
解決思路
通過異常信息大概可以得知是因為url中帶入了“;”符號所以請求地址非法,我們看下最近的報錯的類StrictHttpFirewall 中369行的方法
private void rejectedBlacklistedUrls(HttpServletRequest request) { for (String forbidden : this.encodedUrlBlacklist) {
//如果url中有黑名單的符號就報異常 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 + "\""); } } }
其中有個encodedUrlBlacklist就是所有的非法字符黑名單了,這兒會過濾所有非法字符,我們debug看下里面有哪些
果然第14個有";"符號,按理說springmvc既然有這個注解應該不至於自己定義為非法,查看該類的包名
package org.springframework.security.web.firewall
看包名也很明確了,項目因為使用了spring security有關的東西,這兒應該是spring security默認url不能帶";"符號。有同學會想那我可不可以使用url編碼躲開這個過濾? 當然了框架肯定是想到了這點兒,“;”進行url編碼后為3b%,就是上圖中第8個和第9個 (汗 連大小寫都考慮到了 。。),所以我們肯定還是需要看到底那兒使用的這個類,能不能在項目初始化時將";"從黑名單中移除。
我們在StrictHttpFirewall中搜尋,看下哪些地方涉及到了";"符號 發現如下
public class StrictHttpFirewall implements HttpFirewall { ............ private static final List<String> FORBIDDEN_SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B")); ............ public StrictHttpFirewall() { urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); ... } private void urlBlacklistsAddAll(Collection<String> values) { this.encodedUrlBlacklist.addAll(values); this.decodedUrlBlacklist.addAll(values); } }
可以很明確的看到 該類在初始化的時候就將";" 添加到了encodedUrlBlacklist中,但是該類中還存在一個方法
public void setAllowSemicolon(boolean allowSemicolon) { if (allowSemicolon) { urlBlacklistsRemoveAll(FORBIDDEN_SEMICOLON); } else { urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); } }
該方法目的相信已經相當明確了,調用該方法,就能將";"從黑名單中移除。那解決思路就應該有如下的步驟 ,找到使用該類的地方,然后找到創建對象的地方,調用這個移除黑名單方法將其移出黑名單。
那該如何找到調用的地方呢,其實異常軌跡中已經很明確了,截取前面幾行和security有關的異常方法軌跡,
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";" at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:369) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:336) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:194) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
通過這幾行信息可以發現大概是security在執行過濾器的時候FilterChainProxy對象使用了StrictHttpFirewall類的過濾方法,那我們直接從FilterChainProxy最下面的那個doFilter方法開始看
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (clearContext) { try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); doFilterInternal(request, response, chain); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else { doFilterInternal(request, response, chain); } }
這兒沒有找到和StrictHttpFirewall有關的信息,我們接着看下一個doFilterInternal方法
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //這兒會執行firewall(StrictHttpFirewall)里面的黑名單過濾方法 FirewalledRequest fwRequest = firewall .getFirewalledRequest((HttpServletRequest) request); HttpServletResponse fwResponse = firewall .getFirewalledResponse((HttpServletResponse) response); ............... }
這兒我們看到了和firewall有關的東西了,我們看下這個屬性
private HttpFirewall firewall = new StrictHttpFirewall();
看來就是這個FilterChainProxy對象里默認聲明了一個StrictHttpFirewall並執行了黑名單過濾方法,那我們需要修改的就是這個屬性了,感覺已經成功了一半了。
那我們如何修改這個屬性? 還是可以先看下類里面是否有賦值的方法,我們查看FilterChainProxy對象后發現有如下地方的代碼可以自己設置firwall
public void setFirewall(HttpFirewall firewall) { this.firewall = firewall; }
看來FilterChainProxy中有自己設置HttpFirewall 地方,那我們現在的目的又變成了 找到FilterChainProxy初始化的地方,自己初始化一個StrictHttpFirewall對象並執行其setAllowSemicolon(true)方法,然后將這個firewall塞到FilterChainProxy中。
springsecutiry既然默認禁止 又提供了消除黑名單方法 ,那說明肯定有能配置的地方。我們使用idea全局搜索下,是否有地方使用了這個setFirewall方法,果然就搜索到了
看到這個相信大家就知道應該怎么辦了,使用過spring secutiry的同學相信不會對WebSecurity這個類陌生。這個類保存spring security的一些核心配置。我們看下這個類中setFirewall片段
........... FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (httpFirewall != null) { filterChainProxy.setFirewall(httpFirewall); } filterChainProxy.afterPropertiesSet(); .........
可以發現就是這兒判斷是否有自定義的httpFirewall 如果有就傳入自定義的,如果沒有那就使用上面的FilterChainProxy對象初始化時的StrictHttpFirewall 。
private HttpFirewall httpFirewall;
public WebSecurity httpFirewall(HttpFirewall httpFirewall) { this.httpFirewall = httpFirewall; return this; }
該類是沒有初始化定義防火牆的,但是給了httpFirewall方法可以傳入自定義的HttpFirewall 。所以我們到這兒就找到了最終的解決方案了,就是配置WebSecurity 。熟悉spring secutiry的同學都知道spring secutiry有專門的初始化抽象配置類WebSecurityConfigurerAdapter ,就像springmvc的WebMvcConfigurerAdapter一樣。我們來看下WebSecurityConfigurerAdapter
果然在里面找到了可以配置webSecurity的方法 ,所以我們就直接繼承這個類 然后重寫這個方法
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) throws Exception { StrictHttpFirewall firewall = new StrictHttpFirewall(); //去掉";"黑名單 firewall.setAllowSemicolon(true); //加入自定義的防火牆 web.httpFirewall(firewall); super.configure(web); } }
這個時候重啟項目再調用接口 調用 localhost:8081/api/car/vehicle/test/8008208820;lon=23.203;lat=26.302
已經能夠成功獲取到了