SpringSecurity系列之请求防火墙默认已开启

[[400681]]

之前有小伙伴表示,看 Spring Security 这么麻烦,不如自己写一个 Filter 拦截请求,简单实用。

自己写当然也可以实现,但是大部分情况下,大家都不是专业的 Web 安全工程师,所以考虑问题也不过就是认证和授权,这两个问题处理好了,似乎系统就很安全了。

其实不是这样的!

各种各样的 Web 攻击每天都在发生,什么固定会话攻击、csrf 攻击等等,如果不了解这些攻击,那么做出来的系统肯定也不能防御这些攻击。

使用 Spring Security 的好处就是,即使不了解这些攻击,也不用担心这些攻击,因为 Spring Security 已经帮你做好防御工作了。

我们常说相比于 Shiro,Spring Security 更加重量级,重量级有重量级的好处,比如功能全,安全管理更加完备。用了 Spring Security,你都不知道自己的系统有多安全!

今天我就来和大家聊一聊 Spring Security 中自带的防火墙机制。

好了,不废话了,我们来看文章。

1.HttpFirewall

在 Spring Security 中提供了一个 HttpFirewall,看名字就知道这是一个请求防火墙,它可以自动处理掉一些非法请求。

HttpFirewall 目前一共有两个实现类:

一个是严格模式的防火墙设置,还有一个默认防火墙设置。

DefaultHttpFirewall 的限制相对于 StrictHttpFirewall 要宽松一些,当然也意味着安全性不如 StrictHttpFirewall。

Spring Security 中默认使用的是 StrictHttpFirewall。

2.防护措施

那么 StrictHttpFirewall 都是从哪些方面来保护我们的应用呢?我们来挨个看下。

2.1 只允许白名单中的方法

首先,对于请求的方法,只允许白名单中的方法,也就是说,不是所有的 HTTP 请求方法都可以执行。

这点我们可以从 StrictHttpFirewall 的源码中看出来:

  1. public class StrictHttpFirewall implements HttpFirewall { 
  2.  private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods(); 
  3.  private static Set<String> createDefaultAllowedHttpMethods() { 
  4.   Set<String> result = new HashSet<>(); 
  5.   result.add(HttpMethod.DELETE.name()); 
  6.   result.add(HttpMethod.GET.name()); 
  7.   result.add(HttpMethod.HEAD.name()); 
  8.   result.add(HttpMethod.OPTIONS.name()); 
  9.   result.add(HttpMethod.PATCH.name()); 
  10.   result.add(HttpMethod.POST.name()); 
  11.   result.add(HttpMethod.PUT.name()); 
  12.   return result; 
  13.  } 
  14.  private void rejectForbiddenHttpMethod(HttpServletRequest request) { 
  15.   if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { 
  16.    return
  17.   } 
  18.   if (!this.allowedHttpMethods.contains(request.getMethod())) { 
  19.    throw new RequestRejectedException("The request was rejected because the HTTP method \"" + 
  20.      request.getMethod() + 
  21.      "\" was not included within the whitelist " + 
  22.      this.allowedHttpMethods); 
  23.   } 
  24.  } 

从这段代码中我们看出来,你的 HTTP 请求方法必须是 DELETE、GET、HEAD、OPTIONS、PATCH、POST 以及 PUT 中的一个,请求才能发送成功,否则的话,就会抛出 RequestRejectedException 异常。

那如果你想发送其他 HTTP 请求方法,例如 TRACE ,该怎么办呢?我们只需要自己重新提供一个 StrictHttpFirewall 实例即可,如下:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setUnsafeAllowAnyHttpMethod(true); 
  5.     return firewall; 

其中,setUnsafeAllowAnyHttpMethod 方法表示不做 HTTP 请求方法校验,也就是什么方法都可以过。或者也可以通过 setAllowedHttpMethods 方法来重新定义可以通过的方法。

2.2 请求地址不能有分号

不知掉大家有没有试过,如果你使用了 Spring Security,请求地址是不能有 ; 的,如果请求地址有 ; ,就会自动跳转到如下页面:

可以看到,页面的提示中已经说了,因为你的请求地址中包含 ;,所以请求失败。

什么时候请求地址中会包含 ; 呢?不知道小伙伴们在使用 Shiro 的时候,有没有注意到,如果你禁用了 Cookie,那么 jsessionid 就会出现在地址栏里,像下面这样:

  1. http://localhost:8080/hello;jsessionid=xx 

这种传递 jsessionid 的方式实际上是非常不安全的(松哥后面的文章会和大家细聊这个问题),所以在 Spring Security 中,这种传参方式默认就禁用了。

当然,如果你希望地址栏能够被允许出现 ; ,那么可以按照如下方式设置:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setAllowSemicolon(true); 
  5.     return firewall; 

设置完成之后,再去访问相同的接口,可以看到,此时虽然还是报错,但是错误是 404 了,而不是一开始那个不允许 ; 的错了。

注意,在 URL 地址中,; 编码之后是 %3b 或者 %3B,所以地址中同样不能出现 %3b 或者 %3B

题外话

有的小伙伴可能不知道或者没用过,Spring3.2 开始,带来了一种全新的传参方式 @MatrixVariable。

@MatrixVariable 是 Spring3.2 中带来的功能,这种方式拓展了请求参数的传递格式,使得参数之间可以用 ; 隔开,这种传参方式真是哪壶不开提哪壶。因为 Spring Security 默认就是禁止这种传参方式,所以一般情况下,如果你需要使用 @MatrixVariable 来标记参数,就得在 Spring Security 中额外放行。

接下来我通过一个简单的例子来和大家演示一下 @MatrixVariable 的用法。

我们新建一个 /hello 方法:

  1. @RequestMapping(value = "/hello/{id}"
  2. public void hello(@PathVariable Integer id,@MatrixVariable String name) { 
  3.     System.out.println("id = " + id); 
  4.     System.out.println("name = " + name); 

另外我们还需要配置一下 SpringMVC,使 ; 不要被自动移除了:

  1. @Configuration 
  2. public class WebMvcConfig extends WebMvcConfigurationSupport { 
  3.     @Override 
  4.     protected void configurePathMatch(PathMatchConfigurer configurer) { 
  5.         UrlPathHelper urlPathHelper = new UrlPathHelper(); 
  6.         urlPathHelper.setRemoveSemicolonContent(false); 
  7.         configurer.setUrlPathHelper(urlPathHelper); 
  8.     } 

然后启动项目(注意,Spring Security 中也已经配置了允许 URL 中存在 ;),浏览器发送如下请求:

  1. http://localhost:8080/hello/123;name=javaboy 

控制台打印信息如下:

  1. id = 123 
  2. name = javaboy 

可以看到,@MatrixVariable 注解已经生效了。

2.3 必须是标准化 URL

请求地址必须是标准化 URL。

什么是标准化 URL?标准化 URL 主要从四个方面来判断,我们来看下源码:

StrictHttpFirewall#isNormalized:

  1. private static boolean isNormalized(HttpServletRequest request) { 
  2.  if (!isNormalized(request.getRequestURI())) { 
  3.   return false
  4.  } 
  5.  if (!isNormalized(request.getContextPath())) { 
  6.   return false
  7.  } 
  8.  if (!isNormalized(request.getServletPath())) { 
  9.   return false
  10.  } 
  11.  if (!isNormalized(request.getPathInfo())) { 
  12.   return false
  13.  } 
  14.  return true

getRequestURI 就是获取请求协议之外的字符;getContextPath 是获取上下文路径,相当于是 project 的名字;getServletPath 这个就是请求的 servlet 路径,getPathInfo 则是除过 contextPath 和 servletPath 之后剩余的部分。

这四种路径中,都不能包含如下字符串:

  1. "./""/../" or "/." 

2.4 必须是可打印的 ASCII 字符

如果请求地址中包含不可打印的 ASCII 字符,请求则会被拒绝,我们可以从源码中看出端倪:

StrictHttpFirewall#containsOnlyPrintableAsciiCharacters

  1. private static boolean containsOnlyPrintableAsciiCharacters(String uri) { 
  2.  int length = uri.length(); 
  3.  for (int i = 0; i < length; i++) { 
  4.   char c = uri.charAt(i); 
  5.   if (c < '\u0020' || c > '\u007e') { 
  6.    return false
  7.   } 
  8.  } 
  9.  return true

2.5 双斜杠不被允许

如果请求地址中出现双斜杠,这个请求也将被拒绝。双斜杠 // 使用 URL 地址编码之后,是 %2F%2F,其中 F 大小写无所谓,所以请求地址中也能不出现 “%2f%2f”, “%2f%2F”, “%2F%2f”, “%2F%2F”。

如果你希望请求地址中可以出现 // ,可以按照如下方式配置:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setAllowUrlEncodedDoubleSlash(true); 
  5.     return firewall; 

2.6 % 不被允许

如果请求地址中出现 %,这个请求也将被拒绝。URL 编码后的 % 是 %25,所以 %25 也不能出现在 URL 地址中。

如果希望请求地址中可以出现 %,可以按照如下方式修改:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setAllowUrlEncodedPercent(true); 
  5.     return firewall; 

2.7 正反斜杠不被允许

如果请求地址中包含斜杠编码后的字符 %2F 或者 %2f ,则请求将被拒绝。

如果请求地址中包含反斜杠 \ 或者反斜杠编码后的字符 %5C 或者 %5c ,则请求将被拒绝。

如果希望去掉如上两条限制,可以按照如下方式来配置:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setAllowBackSlash(true); 
  5.     firewall.setAllowUrlEncodedSlash(true); 
  6.     return firewall; 

2.8 . 不被允许

如果请求地址中存在 . 编码之后的字符 %2e、%2E,则请求将被拒绝。

如需支持,按照如下方式进行配置:

  1. @Bean 
  2. HttpFirewall httpFirewall() { 
  3.     StrictHttpFirewall firewall = new StrictHttpFirewall(); 
  4.     firewall.setAllowUrlEncodedPeriod(true); 
  5.     return firewall; 

2.9 小结

需要强调一点,上面所说的这些限制,都是针对请求的 requestURI 进行的限制,而不是针对请求参数。例如你的请求格式是:

  1. http://localhost:8080/hello?param=aa%2ebb 

那么 2.7 小节说的限制和你没关系。

这个大家从 StrictHttpFirewall 源码中很容易看到:

  1. public class StrictHttpFirewall implements HttpFirewall { 
  2.  @Override 
  3.  public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { 
  4.   rejectForbiddenHttpMethod(request); 
  5.   rejectedBlacklistedUrls(request); 
  6.   rejectedUntrustedHosts(request); 
  7.  
  8.   if (!isNormalized(request)) { 
  9.    throw new RequestRejectedException("The request was rejected because the URL was not normalized."); 
  10.   } 
  11.  
  12.   String requestUri = request.getRequestURI(); 
  13.   if (!containsOnlyPrintableAsciiCharacters(requestUri)) { 
  14.    throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters."); 
  15.   } 
  16.   return new FirewalledRequest(request) { 
  17.    @Override 
  18.    public void reset() { 
  19.    } 
  20.   }; 
  21.  } 
  22.  private void rejectedBlacklistedUrls(HttpServletRequest request) { 
  23.   for (String forbidden : this.encodedUrlBlacklist) { 
  24.    if (encodedUrlContains(request, forbidden)) { 
  25.     throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); 
  26.    } 
  27.   } 
  28.   for (String forbidden : this.decodedUrlBlacklist) { 
  29.    if (decodedUrlContains(request, forbidden)) { 
  30.     throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); 
  31.    } 
  32.   } 
  33.  } 
  34.  private static boolean encodedUrlContains(HttpServletRequest request, String value) { 
  35.   if (valueContains(request.getContextPath(), value)) { 
  36.    return true
  37.   } 
  38.   return valueContains(request.getRequestURI(), value); 
  39.  } 
  40.  
  41.  private static boolean decodedUrlContains(HttpServletRequest request, String value) { 
  42.   if (valueContains(request.getServletPath(), value)) { 
  43.    return true
  44.   } 
  45.   if (valueContains(request.getPathInfo(), value)) { 
  46.    return true
  47.   } 
  48.   return false
  49.  } 
  50.  private static boolean valueContains(String value, String contains) { 
  51.   return value != null && value.contains(contains); 
  52.  } 

rejectedBlacklistedUrls 方法就是校验 URL 的,该方法逻辑很简单,我就不再赘述了。

注意:虽然我们可以手动修改 Spring Security 中的这些限制,但是松哥不建议大家做任何修改,每一条限制都有它的原由,每放开一个限制,就会带来未知的安全风险。后面松哥在和大家分享 Web 中的安全攻击时,也会再次提到这些限制的作用,请小伙伴们保持关注哦。

3.总结

没想到吧?Spring Security 竟然为你做了这么多事情!正好应了那句鸡汤:你所谓的岁月静好,不过是有人在替你负重前行。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/129802.html<

(0)
管理的头像管理
上一篇2025-02-25 19:03
下一篇 2025-02-25 19:04

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注