文章目录

  • *`HttpFirewall`*
    • *8.1`HttpFirewall`简介*
    • *8.2`HttpFirewall`严格模式*
      • *8.2.1`rejectForbiddenHttpMethod`*
      • *8.2.2`rejectedBlocklistedUrls`*
      • *8.2.3`rejectedUntrustedHosts`*
      • *8.2.4`isNormalized`*
      • *8.2.5`containsOnlyPrintableAsciiCharacters`*
    • *8.3`HttpFirewall`普通模式*

HttpFirewall

HttpFirewall是spring security提供的HTTP防火墙,它可以用于拒绝潜在的危险请求或者包装这些请求进而控制其行为。HttpFirewall被注入到FilterChainProxy中,并在spring security过滤器链执行之前被触发。

8.1HttpFirewall简介

Spring security中通过HttpFirewall来检查请求路径以及参数是否合法,如果合法,才会进入到过滤器链中进行处理。

public interface HttpFirewall {// 对请求对象进行检验并封装FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException;// 对响应对象进行封装HttpServletResponse getFirewalledResponse(HttpServletResponse response);
}

FirewalledRequest是封装后的请求类,但实际上该类只是在HttpServletRequestWrapper的基础上增加了reset方法。当spring security过滤器链执行完毕时,由FilterChainProxy负责调用该方法,以便重置全部或者部分属性。
FirewalledResponse是封装后的响应类,该类主要重写了sendRedirectsetHeaderaddHeader以及addCookie四个方法,在每一个方法中都对其参数进行校验,以确保参数中不含有\r\n
HttpFirewall一共有两个实现类:

  • DefaultHttpFirewall:虽然名字中包含default,但这并不是框架默认使用的HTTP防火墙,它只是一个检查相对宽松的防火墙。
  • StrictHttpFirewall:这是一个检查严格的HTTP防火墙,默认即此。

HttpFirewall中对请求的合法性校验在FilterChainProxy#doFilterInternal方法中触发。

需要注意的是HttpFirewall的配置位置,在spring security框架中有两个地方涉及了HttpFirewall实例的获取:

  1. FilterChainProxy属性定义中,默认创建的HttpFirewall实例就是StrictHttpFirewall
  2. FilterChainProxy是在WebSecurity#performBuild方法中构建的,而WebSecurity实现了ApplicationContextAware接口,并实现了接口中的setApplicationContext方法,在该方法中,从spring容器中查找到HttpFirewall对并赋值给httpFirewall属性。最终在performBuild方法中,将FilterChainProxy对象构建成功后,如果httpFirewall不为空,就把httpFirewall配置给FilterChainProxy对象。

因此,如果spring容器中存在HttpFirewall实例,则最终使用spring容器提供的实例;如果不存在,则使用FilterChainProxy中默认定义的StrictHttpFirewall

8.2HttpFirewall严格模式

FilterChainProxy#doFilterInternal中触发请求校验的方法如下:

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 请求的校验主要是在getFirewalledRequest方法中完成的。在进入spring security过滤器链之前,请求对象和响应对象// 都分别换成FirewalledRequest和FirewalledResponse了FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);List<Filter> filters = getFilters(firewallRequest);// 省略其他virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

需要注意的是,无论是FirewalledRequest还是FirewalledResponse,在经过spring security过滤器链的时候,还会通过装饰器模式增强其功能,所以开发者最终在接口中拿到的HttpServletRequestHttpServletResponse对象,并不是这里的FirewalledRequestFirewalledResponse
重点分析getFirewalledRequest方法:

// StrictHttpFirewall
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {// 校验请求方法是否合法rejectForbiddenHttpMethod(request);// 校验请求中的非法字符rejectedBlocklistedUrls(request);// 校验主机信息rejectedUntrustedHosts(request);// 判断参数格式是否合法if (!isNormalized(request)) {throw new RequestRejectedException("The request was rejected because the URL was not normalized.");}String requestUri = request.getRequestURI();// 判断请求字符是否合法if (!containsOnlyPrintableAsciiCharacters(requestUri)) {throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters.");}return new StrictFirewalledRequest(request);
}

接下来会逐一分析这五个校验方法。

8.2.1rejectForbiddenHttpMethod

主要用来判断请求方法是否合法:

private void rejectForbiddenHttpMethod(HttpServletRequest request) {if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {return;}if (!this.allowedHttpMethods.contains(request.getMethod())) {throw new RequestRejectedException("The request was rejected because the HTTP method \"" + request.getMethod()+ "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);}
}

allowedHttpMethods是一个Set集合,默认情况下该集合中包含七个常见的方法:DELETEGETHEADOPTIONSPATCHPOSTPUTALLOW_ANY_HTTP_METHOD变量默认情况下则是一个空的Set集合。
开发者可以根据实际需求修改allowedHttpMethods变量的值,进而调整允许的请求方法。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 第一种方式:通过设置allowedHttpMethods进行修改,此时只允许POST请求。*/// @Bean// HttpFirewall httpFirewall() {//     StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();//     Set<String> allowedHttpMethods = new HashSet<>();//     allowedHttpMethods.add(HttpMethod.POST.name());//     strictHttpFirewall.setAllowedHttpMethods(allowedHttpMethods);//     return strictHttpFirewall;// }/*** 第二种方式:设置参数为true,让allowedHttpMethods等于ALLOW_ANY_HTTP_METHOD,进而允许所有请求通过。*/@BeanHttpFirewall httpFirewall() {StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();strictHttpFirewall.setUnsafeAllowAnyHttpMethod(true);return strictHttpFirewall;}// 省略其他
}
8.2.2rejectedBlocklistedUrls

主要用来校验请求URL是否规范:

  1. 如果请求URL地址中在编码之前或者之后,包含了分号,即;%3b%3B,则该请求会被拒绝,可以通过setAllowSemicolon方法开启或者关闭这一规则。
  2. 如果请求URL地址中在编码之前或者之后,包含了斜杠,即%2f%2F,则该请求会被拒绝,可以通过setAllowUrlEncodedSlash方法开启或者关闭这一规则。
  3. 如果请求URL地址中在编码之前或者之后,包含了反斜杠,即\\%5c%5C,则该请求会被拒绝,可以通过setAllowBackSlash方法开启或者关闭这一规则。
  4. 如果请求URL地址中在编码之后包含了%25或者在编码之前包含了%,则该请求会被拒绝,可以通过setAllowUrlEncodedPercent方法开启或者关闭这一规则。
  5. 如果请求URL在编码后包含了英文句号%2e或者%2E,则该请求会被拒绝,可以通过setAllowUrlEncodedPeriod方法开启或者关闭这一规则。
private void rejectedBlocklistedUrls(HttpServletRequest request) {// 校验编码后的请求地址for (String forbidden : this.encodedUrlBlocklist) {// 主要是校验了contextPath和requestURI两个属性,这两个属性是客户端传递来的字符串,未做任何更改if (encodedUrlContains(request, forbidden)) {throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \""+ forbidden + "\"");}}// 校验解码后的请求地址for (String forbidden : this.decodedUrlBlocklist) {// 主要校验了servletPath、pathInfo两个属性,需要注意的是,这个是经过解码后的if (decodedUrlContains(request, forbidden)) {throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \""+ forbidden + "\"");}}
}
8.2.3rejectedUntrustedHosts

主要用来校验host是否受信任:

private void rejectedUntrustedHosts(HttpServletRequest request) {String serverName = request.getServerName();if (serverName != null && !this.allowedHostnames.test(serverName)) {throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");}
}

allowedHostnames默认总是返回true,即默认信任所有的host,可以根据实际需求对此进行配置:

@Bean
HttpFirewall httpFirewall() {StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();strictHttpFirewall.setAllowedHostnames((hostname) -> hostname.equalsIgnoreCase("local.javaboy.org"));return strictHttpFirewall;
}
8.2.4isNormalized

主要用来检查请求地址是否规范,即不包含"./"、"/…/“以及”/."三种字符。

private static boolean isNormalized(HttpServletRequest request) {// 对requestURI、contextPath、servletPath以及pathInfo分别进行了校验if (!isNormalized(request.getRequestURI())) {return false;}if (!isNormalized(request.getContextPath())) {return false;}if (!isNormalized(request.getServletPath())) {return false;}if (!isNormalized(request.getPathInfo())) {return false;}return true;
}
8.2.5containsOnlyPrintableAsciiCharacters

用来校验请求地址中是否包含不可打印的ASCII字符。

private static boolean containsOnlyPrintableAsciiCharacters(String uri) {int length = uri.length();for (int i = 0; i < length; i++) {char ch = uri.charAt(i);if (ch < '\u0020' || ch > '\u007e') {return false;}}return true;
}

StrictHttpFirewall中的校验规则,前三种可以通过相关方法调整,后面两种不可调整。

8.3HttpFirewall普通模式

HttpFirewall普通模式就是使用DefaultHttpFirewall,该类的校验规则就要简单很多:

// DefaultHttpFirewall
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {// 构建RequestWrapper对象对原始请求中的功能进行了增强,例如将请求地址中的//格式化为/,以及将请求中的servletPath和// pathInfo中用分号隔开的参数提取出来,只保留路径即可FirewalledRequest firewalledRequest = new RequestWrapper(request);if (!isNormalized(firewalledRequest.getServletPath()) || !isNormalized(firewalledRequest.getPathInfo())) {throw new RequestRejectedException("Un-normalized paths are not supported: " + firewalledRequest.getServletPath()+ ((firewalledRequest.getPathInfo() != null) ? firewalledRequest.getPathInfo() : ""));}String requestURI = firewalledRequest.getRequestURI();// 判断requestURI中是否包含编码后的斜杠if (containsInvalidUrlEncodedSlash(requestURI)) {throw new RequestRejectedException("The requestURI cannot contain encoded slash. Got " + requestURI);}return firewalledRequest;
}

一般来说,并不建议在项目中使用DefaultHttpFirewall,如果一定要用,只需要提供一个实例即可:

@Bean
HttpFirewall httpFirewall() {return new DefaultHttpFirewall();
}

8.Spring security中的HttpFirewall相关推荐

  1. Spring Security中文文档

    Spring Security中文文档 来源:https://www.springcloud.cc/spring-security.html#overall-architecture 作者 Ben A ...

  2. Spring Security 中最流行的权限管理模型!

    前面和大家说了 ACL,讲了理论,也给了一个完整的案例,相信小伙伴们对于 ACL 权限控制模型都已经比较了解了. 本文我要和大家聊一聊另外一个非常流行的权限管理模型,那就是 RBAC. 1.RBAC ...

  3. 一起搞清楚 Spring Security 中的 UserDetails

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 170元买400元书的机会又来啦! 1. 前言 前一篇介绍了 Spring Security ...

  4. springsecurity sessionregistry session共享_要学就学透彻!Spring Security 中 CSRF 防御源码解析...

    今日干货 刚刚发表查看:66666回复:666 公众号后台回复 ssm,免费获取松哥纯手敲的 SSM 框架学习干货. 上篇文章松哥和大家聊了什么是 CSRF 攻击,以及 CSRF 攻击要如何防御.主要 ...

  5. Spring Security 中取得 RememberMe 的 cookie 值

    为什么80%的码农都做不了架构师?>>>    Spring Security 中的 RememberMe 对应的 cookie 名称是可配置的--相信一般情况下大家也不会使用那个默 ...

  6. Http基本身份验证在Spring Security中如何工作?

    在上一篇文章中,您学习了如何在基于Spring安全性的Java应用程序中启用Http基本身份验证 ,现在,我们将进一步进一步了解http基本身份验证在Spring安全性中的工作原理. 如果您还记得的话 ...

  7. Spring Security中的SecurityContext和SecurityContextHolder是什么?

    SecurityContext和SecurityContextHolder是Spring Security的两个基本类. SecurityContext用于存储当前经过身份验证的用户的详细信息,也称为 ...

  8. 如何使用Java和XML Config在Spring Security中启用HTTP基本身份验证

    在上一篇文章中,我向您展示了如何在Java应用程序中启用Spring安全性 ,今天我们将讨论如何使用Spring Security 在Java Web应用程序中启用Basic HTTP身份验证 . 如 ...

  9. java按钮权限控制_详解Spring Security 中的四种权限控制方式

    Spring Security 中对于权限控制默认已经提供了很多了,但是,一个优秀的框架必须具备良好的扩展性,恰好,Spring Security 的扩展性就非常棒,我们既可以使用 Spring Se ...

最新文章

  1. 属于窄带噪声的是热噪声_时钟201系列: 非相位噪声的情况 (第一篇)
  2. Windows核心编程 第27章 硬件输入模型和局部输入状态
  3. 拦截器 java_在Java后端如何添加拦截器
  4. python安装pyinstaller出现错误_pyinstaller 3.6版本通过pip安装失败的解决办法(推荐)...
  5. rsync 服务快速部署手册
  6. maven jar包冲突的发现与解决[工具篇]
  7. Python3_基础部分_第一个Python程序
  8. python中button中command_tkinter模块的button控件不点击也运行command
  9. php 小说采集系统,YGBOOK小说采集系统 php版 v1.4
  10. 久其报表软件基本操作指引
  11. matlab找零钱,自动售货系统
  12. APIO 2017 考拉的游戏 题解
  13. iOS开发-ios7样式绕圈活动指示器(自定义Activity Indicator View)
  14. 【金融】新成立基金建仓时点、行业分布与市场行情关系探究
  15. REDIS哨兵【Sentinel】模式+哨兵的核心知识点+redis哨兵主从切换的数据丢失问题+上一章铺垫的【异步复制数据丢失问题】+【集群脑裂】
  16. 计算机毕业设计ssm基于ssm流浪宠物领养系统8xg84系统+程序+源码+lw+远程部署
  17. es时间对象(Date)
  18. 联想微型计算机安装系统,联想笔记本做系统,手把手教你联想笔记本安装win10系统...
  19. 劳动合同到期提醒怎么设置,怎么在便签上设置合同到期提醒
  20. 【第五届集创赛备赛】一、黄乐天老师赛事宣讲及各个赛题分析

热门文章

  1. python 数据库性能提升 - TCP聊天+传输文件服务器服务器套接字v2.7
  2. ICCV 2021 | High-Fidelity Pluralistic Image Completion with Transformers 阅读笔记(部分翻译)
  3. 程序员如何正确应对压力
  4. Dockerfile RUN指令 语法解析
  5. 解决No architectures to compile for...
  6. Appium学习日记(一)——Appium工作原理及其主要组件
  7. redis根据前缀批量查找key
  8. 【Paper Note】基于情感分析和关系网络的影视产品评论数据文本挖掘研究
  9. 20189215 2018-2019-2 《密码与安全新技术专题》课程总结
  10. 最简单的JAVA解法-----无聊的逗