此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

Http防火墙

了解该机制以及在针对您定义的模式进行测试时所使用的URL值是非常重要的。spring-doc.cadn.net.cn

Servlet 规范定义了 HttpServletRequest 的多个属性,这些属性可通过 getter 方法访问,我们可能希望对它们进行匹配。这些是contextPath, servletPath, pathInfo, 和 queryString。Spring Security 只对应用程序内部的路径进行安全保护,因此contextPath被忽略。不幸的是,Servlet 规范并没有确切定义对于某个请求URI来说servletPathpathInfo的值具体是什么。例如,URL 的每个路径段可能包含参数,如 RFC 2396 中所定义。 (您可能在浏览器不支持 Cookie 时见过这种情况,此时 jsessionid 参数会在分号后附加到 URL 中。)然而,RFC 允许这些参数出现在 URL 的任意路径段中。) The Specification 不清楚地说明了这些是否应包含在 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 执行不区分大小写的模式匹配,并忽略 queryStringspring-doc.cadn.net.cn

如果您需要更强大的匹配策略,可以使用正则表达式。 该策略的实现为 RegexRequestMatcher。 有关更多信息,请参阅 RegexRequestMatcher Javadoc。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 的矩阵变量(matrix variables),可以使用以下配置: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 方法白名单。 默认的有效方法是 DELETEGETHEADOPTIONSPATCHPOSTPUT。 如果您的应用程序需要修改有效方法,可以配置自定义的 StrictHttpFirewall Bean。 以下示例仅允许 HTTP GETPOST 方法: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_16851,该问题请求对此进行改进。spring-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

例如,要关闭此检查,您可以将StrictHttpFirewall与总是返回Predicatetrue实例进行连接: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
}

对于头部(header)值,您可以考虑在验证时将其解析为 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()
}