不知道这是Spring 5.0.3的错误还是新功能可以修复我的问题。
升级后,出现此错误。有趣的是,此错误仅在我的本地计算机上。使用HTTPS协议的测试环境中的相同代码可以正常工作。
继续…
我收到此错误的原因是因为我用于加载结果JSP页面的URL是/location/thisPage.jsp。评估代码request.getRequestURI()会给我结果/WEB-INF/somelocation//location/thisPage.jsp。如果我将JSP页面的URL修复为此location/thisPage.jsp,则一切正常。
/location/thisPage.jsp
request.getRequestURI()
/WEB-INF/somelocation//location/thisPage.jsp
location/thisPage.jsp
所以我的问题是,我应该/从JSP代码的路径中删除吗,因为这是今后的要求。或者Spring引入了一个错误,因为我的机器和测试环境之间的唯一区别是协议HTTP与HTTPS。
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized. at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:123) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:194) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
Spring Security Documentation提到了阻塞请求的原因。
例如,它可能包含路径遍历序列(例如/../)或多个正斜杠(//),这也可能导致模式匹配失败。一些容器在执行servlet映射之前将它们标准化,但其他容器则没有。为了避免此类问题,FilterChainProxy使用HttpFirewall策略检查并包装请求。默认情况下,未规范化的请求将自动被拒绝,并且出于匹配目的,将删除路径参数和重复的斜杠。
因此,有两种可能的解决方案-
@Bean public HttpFirewall allowUrlEncodedSlashHttpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); return firewall; }
步骤2,然后在Websecurity中配置此bean
@Override public void configure(WebSecurity web) throws Exception { //@formatter:off super.configure(web); web.httpFirewall(allowUrlEncodedSlashHttpFirewall()); .... }
步骤2是可选步骤,Spring Boot仅需要声明一个类型为bean的bean HttpFirewall
我实现了一个StrictHttpFirewall将请求信息记录到控制台的子类,并抛出了一个带有抑制的堆栈跟踪的新异常。这部分解决了我的问题(至少现在我可以看到错误的请求)。
StrictHttpFirewall
如果您只想查看没有堆栈跟踪的拒绝的请求,这就是您想要的答案。
如果要在控制器中处理这些异常,请参阅公认的答案以获取完整(但稍微复杂一点)的解决方案。
LoggingHttpFirewall.java
此类扩展了StrictHttpFirewall来捕获RequestRejectedException并引发新异常,该异常来自请求中的元数据,并且堆栈跟踪被抑制。
RequestRejectedException
import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.security.web.firewall.StrictHttpFirewall; /** * Overrides the StrictHttpFirewall to log some useful information about blocked requests. */ public final class LoggingHttpFirewall extends StrictHttpFirewall { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(LoggingHttpFirewall.class.getName()); /** * Default constructor. */ public LoggingHttpFirewall() { super(); return; } /** * Provides the request object which will be passed through the filter chain. * * @returns A FirewalledRequest (required by the HttpFirewall interface) which * inconveniently breaks the general contract of ServletFilter because * we can't upcast this to an HttpServletRequest. This prevents us * from re-wrapping this using an HttpServletRequestWrapper. * @throws RequestRejectedException if the request should be rejected immediately. */ @Override public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) throws RequestRejectedException { try { return super.getFirewalledRequest(request); } catch (RequestRejectedException ex) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString()); } // Wrap in a new RequestRejectedException with request metadata and a shallower stack trace. throw new RequestRejectedException(ex.getMessage() + ".\n Remote Host: " + request.getRemoteHost() + "\n User Agent: " + request.getHeader("User-Agent") + "\n Request URL: " + request.getRequestURL().toString()) { private static final long serialVersionUID = 1L; @Override public synchronized Throwable fillInStackTrace() { return this; // suppress the stack trace. } }; } } /** * Provides the response which will be passed through the filter chain. * This method isn't extensible because the request may already be committed. * Furthermore, this is only invoked for requests that were not blocked, so we can't * control the status or response for blocked requests here. * * @param response The original HttpServletResponse. * @return the original response or a replacement/wrapper. */ @Override public HttpServletResponse getFirewalledResponse(final HttpServletResponse response) { // Note: The FirewalledResponse class is not accessible outside the package. return super.getFirewalledResponse(response); } }
WebSecurityConfig.java
在中WebSecurityConfig,将HTTP防火墙设置为LoggingHttpFirewall。
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * Default constructor. */ public WebSecurityConfig() { super(); return; } @Override public final void configure(final WebSecurity web) throws Exception { super.configure(web); web.httpFirewall(new LoggingHttpFirewall()); // Set the custom firewall. return; } }
结果
在将该解决方案部署到生产环境后,我很快发现的默认行为StrictHttpFirewall是阻止Google将我的网站编入索引!
Aug 13, 2018 1:48:56 PM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest WARNING: Intercepted RequestBlockedException: Remote Host: 66.249.64.223 User Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Request URL: https://www.mycompany.com/10.1601/tx.3784;jsessionid=692804549F9AB55F45DBD0AFE2A97FFD
一旦发现这一点,我便迅速部署了一个新版本(包含在我的其他答案中)来查找;jsessionid=并允许这些请求通过。可能还有其他一些请求也应该通过,现在我可以检测到这些请求。
jsessionid=