IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 8.Spring security中的HttpFirewall -> 正文阅读

[Java知识库]8.Spring security中的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进行修改
     * @return
     */
    // @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,进而允许所有请求通过
     * @return
     */
    @Bean
    HttpFirewall httpFirewall() {
        StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
        strictHttpFirewall.setUnsafeAllowAnyHttpMethod(true);
        return strictHttpFirewall;
    }

    // 省略其他
}
8.2.2rejectedBlocklistedUrls

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

  1. 如果请求URL地址中在编码之前或者之后,包含了分号,则该请求会被拒绝,可以通过setAllowSemicolon方法开启或者关闭这一规则。
  2. 如果请求URL地址中在编码之前或者之后,包含了斜杠,则该请求会被拒绝,可以通过setAllowUrlEncodedSlash方法开启或者关闭这一规则。
  3. 如果请求URL地址中在编码之前或者之后,包含了反斜杠,则该请求会被拒绝,可以通过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普通模式

// DefaultHttpFirewall
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
    // 对原始请求中的功能进行了增强,例如将请求地址中的//格式化为/,以及将请求中的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();
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:18:07  更:2022-03-12 17:21:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:00:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码