一、HttpFirewall
Spring Security有几个领域,你所定义的 pattern 会针对传入的请求进行测试,以决定应该如何处理请求。这发生在 FilterChainProxy 决定请求应该通过哪个过滤链时,以及 FilterSecurityInterceptor 决定哪些安全约束适用于请求时。在针对你定义的 pattern 进行测试时,了解该机制是什么以及使用什么URL值是很重要的。
servlet规范为 HttpServletRequest 定义了几个属性,这些属性可以通过 getter 方法访问,我们可能想与之匹配。这些属性是 contextPath、servletPath、pathInfo 和 queryString。Spring Security 只对应用程序中的路径安全感兴趣,所以 contextPath 被忽略了。不幸的是,servlet规范并没有准确地定义 servletPath 和 pathInfo` 的值对于一个特定的请求URI包含什么。例如,URL的每个路径段都可能包含参数,正如 RFC 2396 所定义的那样(你可能已经看到,当浏览器不支持cookies时,jsessionid 参数被附加到URL的分号之后。然而,RFC允许在URL的任何路径段中出现这些参数)。该规范没有明确说明这些参数是否应该包含在 servletPath 和 pathInfo 的值中,而且不同的servlet容器之间的行为也不同。存在这样一种危险:当应用程序部署在不从这些值中剥离路径参数的容器中时,攻击者可以将它们添加到请求的 URL 中,从而导致 pattern 匹配意外地成功或失败。一旦请求离开 FilterChainProxy,原始值将被返回,所以应用程序仍然可以使用)。传入的URL也有可能出现其他变化。例如,它可能包含路径遍历序列(如 /../)或多个正斜杠(//),也可能导致 pattern 匹配失败。一些容器在执行servlet映射之前会将这些内容规范化,但其他容器则不会。为了防止类似的问题,FilterChainProxy 使用 HttpFirewall 策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,路径参数和重复的斜线也会被删除,以便匹配。因此,例如,一个原始的请求路径为 /secure;hack=1/somefile.html;hack=2,被返回为 /secure/somefile.html)。因此,必须使用 FilterChainProxy 来管理安全过滤器链。请注意, servletPath 和 pathInfo 的值是由容器解码的,所以你的应用程序不应该有任何包含分号的有效路径,因为这些部分会被移除用于匹配目的。
如前所述,默认策略是使用 Ant 风格的路径进行匹配,这可能是大多数用户的最佳选择。该策略在 AntPathRequestMatcher 类中实现,该类使用Spring的 AntPathMatcher对servletPath 和 pathInfo 的连接模式进行不区分大小写的匹配,忽略 queryString。
如果你需要一个更强大的匹配策略,你可以使用正则表达式。那么这个策略的实现就是 RegexRequestMatcher。参见 该类的Javadoc以了解更多信息。
在实践中,我们建议你在服务层使用方法安全,以控制对你的应用程序的访问,而不是完全依赖使用在Web应用层定义的安全约束。URL会发生变化,而且很难考虑到一个应用程序可能支持的所有可能的URL,以及请求可能被操纵的方式。你应该限制自己使用一些简单的Ant路径,这些路径很容易理解。始终尝试使用 "deny-by-default" 的方法,在这里你有一个万能的通配符(/** 或 **)最后定义来拒绝访问。
在服务层定义的安全更强大,更难绕过,所以你应该始终利用Spring Security的方法安全选项。
HttpFirewall 还通过拒绝HTTP响应头中的换行字符来防止 HTTP响应分裂。
默认情况下,使用 StrictHttpFirewall 实现。这个实现会拒绝那些看起来是恶意的请求。如果它对你的需求来说过于严格,你可以自定义拒绝哪些类型的请求。然而,重要的是,你要知道这样做会使你的应用程序受到攻击。例如,如果你希望使用Spring MVC的 matrix 变量,你可以使用以下配置。
Allow Matrix Variables
-
Java
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
return firewall;
}
为了防止 跨站追踪(XST) 和 HTTP Verb Tampering,StrictHttpFirewall 提供了一个允许的有效 HTTP 方法列表。默认的有效方法是 DELETE、GET、HEAD、OPTIONS、PATCH、POST 和 `PUT。如果你的应用程序需要修改有效方法,你可以配置一个自定义的 StrictHttpFirewall Bean。下面的例子只允许HTTP GET 和 POST 方法。
Allow Only GET & POST
-
Java
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
return firewall;
}
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 如果你使用 new MockHttpServletRequest(),它目前创建的HTTP方法是一个空字符串("")。这是一个无效的HTTP方法,会被Spring Security拒绝。你可以用 new MockHttpServletRequest("GET", "") 代替它来解决这个问题。请参阅 SPR_16851,该问题要求改进这一点。 |
如果你必须允许任何 HTTP 方法(不推荐),你可以使用 StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)。这样做可以完全禁止对HTTP方法的验证。
StrictHttpFirewall 还检查头名称和值以及参数名称。它要求每个字符都有一个定义好的码位(code point),并且不是一个控制字符。
这一要求可以通过以下方法在必要时放宽或调整。
- StrictHttpFirewall#setAllowedHeaderNames(Predicate)
- StrictHttpFirewall#setAllowedHeaderValues(Predicate)
- StrictHttpFirewall#setAllowedParameterNames(Predicate)
|---------------------------------------------------|
| 参数值也可以用 setAllowedParameterValues(Predicate) 来控制。 |
例如,为了关闭这个检查,你可以在你的 StrictHttpFirewall 中加入总是返回 true 的 Predicate 实例。
Allow Any Header Name, Header Value, and Parameter Name
-
Java
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHeaderNames((header) -> true);
firewall.setAllowedHeaderValues((header) -> true);
firewall.setAllowedParameterNames((parameter) -> true);
return firewall;
}
另外,可能有一个特定的值,你需要允许。
例如,iPhone Xʀ 使用的 User-Agent 包括一个不属于 ISO-8859-1 字符集的字符。由于这一事实,一些应用服务器将这个值解析为两个独立的字符,后者是一个未定义的字符。
你可以用 setAllowedHeaderValues 方法解决这个问题。
Allow Certain User Agents
-
Java
@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;
}
如果是 header 值,你可以考虑在验证时将其解析为UTF-8。
Parse Headers As UTF-8
-
Java
firewall.setAllowedHeaderValues((header) -> {
String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
return allowed.matcher(parsed).matches();
});
二、重定向到 HTTPS
如果客户端使用HTTP而不是HTTPS发出请求,你可以配置Spring Security重定向到HTTPS。
例如,下面的Java或Kotlin配置将任何HTTP请求重定向到HTTPS。
Redirect to HTTPS
-
Java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // ... .requiresChannel(channel -> channel .anyRequest().requiresSecure() ); return http.build(); }
}
下面的XML配置将所有HTTP请求重定向到HTTPS
Redirect to HTTPS with XML Configuration
<http>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
...
</http>