HTTP 访问限制

1. 前言

我们知道 Web 容器的实现有很多种,比如 Tomcat,WebSphere,WebLogic,JBoss 等,不同的 Web 容器在处理 URL 的规则上会有所不同,例如有些容器没有上下文的概念,有些容器不支持路径参数等。为了化解这些差异性,Spring Security 提供了对请求的过滤筛选机制。

Spring Security 的部分模块,会根据我们所定义的模式,来检查接收到的请求,以此决定该请求是否需要被处理。例如 FilterChainProxy 决定请求可以通过哪些过滤器链,FilterSecurityInterecptor 请求在触发哪些安全约束时会发生允许或拒绝的情况。所以对于开发者来说,理解其机制并且了解哪些 URL 适配哪些自定义模式就显得尤为重要。

本节主要讨论 Spring Security 中请求的筛选机制。

2. 关于 HTTP 防火墙

Servlet 规范中已经为 HttpServletRequest 定义了一些属性,这些属性通过 Getter 方法访问,并用作匹配处理。这些属性包括:contextPathservletPathpathInfoqueryString

图片描述

Spring Security 仅关心应用程序的路径部分,并不关心 contextPath。另一方面,在 Servlet 的规范中,缺少对 servletPathpathInfo 的规定,比如 URL 中每个路径段都可能包含参数,然而这些参数是否应该算作 servletPath 或者 pathInfo 值中,规范却没有明确说明,并且在不同的 Servlet 容器中,其处理行为也不尽相同。当应用程序被部署在不从路径中解析参数的容器中时,攻击者可能将路径参数添加到请求的 URL 中,从而导致模式匹配的成功或者失败。还有另一种情况,路径中可能包含一些如遍历 /../ 或者多个连续正斜杠 // 此类的内容,这也可能导致模式匹配的失效。有的容器在执行 Servlet 映射之前对其做了规范化处理,但不是所有容器都是。默认情况下,这些容器会自动拒绝未规范化的请求,并删除路径参数和重复斜杠。所以,为了保证程序在不同环境的一致性,我们就需要使用 FilterChainProxy 来管理安全过滤器链。还要注意一点,servletPathpathInfo 是由容器解析得出的,因此我们还要避免使用分号。

路径的默认匹配策略使用了 Ant 风格,这也是最为常用的一种匹配模式。这个策略是由类 AntPathRequestMatcher 实现的,在 Spring 中由 AntPathMatcher 负责对 servletPathpathInfo 属性执行不区分大小写的模式匹配,此过程中不处理 queryString。

有时候,我们会需要更复杂的匹配策略,比如正则表达式,这时候就需要用到 RegexRequestMatcher 对象了。

URL 匹配并不适合作为访问控制的唯一策略,我们还需要在服务层使用方法安全性来确保其安全性。由于 URL 是富于变化的,所以我们很难涵盖所有情况,最好的办法是采用白名单方式,只允许确认可用的地址被访问。

图片描述

3. Spring Security 实现

在 Spring Security 项目中,默认使用 StrictHttpFirewall 对象,该对象对一些疑似恶意攻击的请求也进行了拒绝处理。假如该对象对我们的项目来说过于严格,那我们可以通过配置的方式定制哪些请求需要被拒绝,当然相应的,我们的应用程序也更容易受到攻击。

我们可以才用以下方式变更配置,如允许分号:

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}

StrictHttpFirewall 对象提供了一个允许被跨域访问的 HTTP 方法列表,默认允许的方法有:

  • DELETE
  • GET
  • HEAD
  • OPTIONS
  • PATCH
  • POST
  • PUT

如果希望修改此项默认策略,我们可以通过自定义 StrictHttpFirewall 对象实现。

例如,仅允许 GETPOST 方法:

@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}

也可以通过如下方法禁用所有方法的验证功能:

StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)

4. 小结

本节讨论了 Spring Security 对请求的过滤和筛选处理,主要内容如下:

  • HttpFirewall 接口是不同容器在没有统一规范的情况下,实现了对请求地址的一致化处理结果;
  • HttpFirewall 的默认应用对象是 StrictHttpFirewall,该对象采用了严格模式,对所有可疑请求都会进行拒绝处理;
  • Spring Security 提供了针对 StrictHttpFirewall 的配置入口,我们可以针对应用的安全需求定制化地址匹配方案。

下节讨论 Spring Security 中的加密方法。