此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3spring-doc.cadn.net.cn

Http防火墙

了解机制是什么以及根据您定义的模式进行测试时使用的 URL 值非常重要。spring-doc.cadn.net.cn

servlet 规范为HttpServletRequest可以通过 getter 方法访问,并且我们可能想要匹配。 这些是contextPath,servletPath,pathInfoqueryString. Spring Security 只对保护应用程序中的路径感兴趣,因此contextPath被忽略。 不幸的是,servlet 规范并没有准确定义servletPathpathInfoinclude 的特定请求 URI。 例如,URL 的每个路径段可能包含 RFC 2396 中定义的参数(当浏览器不支持 cookie 和jsessionid参数附加到分号后面的 URL。 但是,RFC 允许在 URL 的任何路径段中存在这些参数。 规范没有明确说明这些是否应该包含在servletPathpathInfo值,行为在不同的 servlet 容器之间有所不同。 存在一种危险,即当应用程序部署在未从这些值中剥离路径参数的容器中时,攻击者可能会将它们添加到请求的 URL 中,从而导致模式匹配意外成功或失败。 (一旦请求离开FilterChainProxy,因此仍可用于应用程序。 传入 URL 的其他变体也是可能的。 例如,它可以包含路径遍历序列(例如/../) 或多个正斜杠 () 也可能导致模式匹配失败。 一些容器在执行 servlet 映射之前对这些进行规范化,但其他容器则不会。 为了防止此类问题,//FilterChainProxy使用HttpFirewall策略来检查和包装请求。 默认情况下,系统会自动拒绝未规范化的请求,并删除路径参数和重复斜杠以进行匹配。 (因此,例如,原始请求路径/secure;hack=1/somefile.html;hack=2返回为/secure/somefile.html.) 因此,必须FilterChainProxy用于管理安全过滤器链。 请注意,servletPathpathInfo值由容器解码,因此应用程序不应具有任何包含分号的有效路径,因为出于匹配目的,这些部分将被删除。spring-doc.cadn.net.cn

前面说过,默认策略是使用 Ant 风格的路径进行匹配,这对于大多数用户来说可能是最佳选择。 该策略在类中实现PathPatternRequestMatcher,它使用 Spring 的PathPattern对模式与串联的servletPathpathInfo,忽略queryString.spring-doc.cadn.net.cn

如果您需要更强大的匹配策略,可以使用正则表达式。 然后战略实施RegexRequestMatcher. 请参阅RegexRequestMatcherJavadoc 了解更多信息。spring-doc.cadn.net.cn

在实践中,我们建议您在服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于使用 Web 应用程序级别定义的安全约束。 URL 会发生变化,并且很难考虑应用程序可能支持的所有可能的 URL 以及如何作请求。 您应该限制自己使用一些简单易懂的 Ant 路径。 始终尝试使用“默认拒绝”方法,其中有一个包罗万象的通配符 (/) 定义最后以拒绝访问。spring-doc.cadn.net.cn

在服务层定义的安全性更加健壮且更难绕过,因此您应该始终利用 Spring Security 的方法安全选项。spring-doc.cadn.net.cn

HttpFirewall还通过拒绝 HTTP 响应标头中的换行符来防止 HTTP 响应拆分spring-doc.cadn.net.cn

默认情况下,StrictHttpFirewall使用实现。 此实现会拒绝看似恶意的请求。 如果它对您的需求来说太严格,您可以自定义拒绝的请求类型。 但是,请务必知道这可能会使您的应用程序受到攻击。 例如,如果您希望使用 Spring MVC 的矩阵变量,您可以使用以下配置:spring-doc.cadn.net.cn

允许矩阵变量
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
<b:bean id="httpFirewall"
    class="org.springframework.security.web.firewall.StrictHttpFirewall"
    p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}

为了防止跨站点跟踪 (XST)HTTP 谓词篡改,请StrictHttpFirewall提供了允许的有效 HTTP 方法的允许列表。 默认的有效方法是DELETE,GET,HEAD,OPTIONS,PATCH,POSTPUT. 如果您的应用程序需要修改有效方法,您可以配置自定义StrictHttpFirewall豆。 以下示例仅允许 HTTPGETPOST方法:spring-doc.cadn.net.cn

仅允许 GET 和 POST
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowedHttpMethods="GET,POST"/>

<http-firewall ref="httpFirewall"/>
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}

如果您使用new MockHttpServletRequest(),它当前将 HTTP 方法创建为空字符串 ()。 这是一个无效的HTTP方法,被Spring Security拒绝。 您可以通过将其替换为""new MockHttpServletRequest("GET", ""). 有关要求改进此问题的问题,请参阅SPR_16851spring-doc.cadn.net.cn

如果必须允许任何 HTTP 方法(不推荐),则可以使用StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true). 这样做会完全禁用 HTTP 方法的验证。spring-doc.cadn.net.cn

StrictHttpFirewall还检查标头名称和值以及参数名称。 它要求每个字符都有一个定义的代码点,而不是控制字符。spring-doc.cadn.net.cn

可以使用以下方法根据需要放宽或调整此要求:spring-doc.cadn.net.cn

参数值也可以通过setAllowedParameterValues(Predicate).spring-doc.cadn.net.cn

例如,要关闭此检查,您可以将StrictHttpFirewallPredicate始终返回的实例true:spring-doc.cadn.net.cn

允许任何标头名称、标头值和参数名称
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHeaderNames { true }
    firewall.setAllowedHeaderValues { true }
    firewall.setAllowedParameterNames { true }
    return firewall
}

或者,可能需要允许特定值。spring-doc.cadn.net.cn

例如,iPhone X 使用User-Agent包括不在 ISO-8859-1 字符集中的字符。 由于这个事实,一些应用程序服务器将此值解析为两个单独的字符,后者是未定义的字符。spring-doc.cadn.net.cn

您可以使用setAllowedHeaderValues方法:spring-doc.cadn.net.cn

允许某些用户代理
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
    Pattern userAgent = ...;
    firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
    return firewall;
}
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
    val userAgent = Pattern.compile(...)
    firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
    return firewall
}

对于标头值,您可以考虑在验证时将它们解析为 UTF-8:spring-doc.cadn.net.cn

将标头解析为 UTF-8
firewall.setAllowedHeaderValues((header) -> {
    String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
    return allowed.matcher(parsed).matches();
});
firewall.setAllowedHeaderValues {
    val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
    return allowed.matcher(parsed).matches()
}