Spring Security(学习笔记) --HttpFirewall防火墙!

重点标识

HttpFirewall是Spring Security提供的防火墙,主要作用就是拒绝潜在危险请求以及包装这些请求,进而控制的行为。
通过HttpFirewall可以对非法请求提前进行拦截,降低损失。

简单模式
严格模式

简介

我们知道,在Servlet容器规范中,为HttpServletRequest定义了一些属性,如ContextPath.ServletPath等,这些属性我们都可以通过get方法获得,但是,在不同的容器中,对这些属性的值处理方案也不同,有些容器会对属性的值进行预处理,有些则不会,这样就比较混乱了,不同容器的差异,有可能会造成不安全的隐患。

因此,Spring Security就提供了HttpFirewall来统一进行管理。

public interface HttpFirewall {

//对请求对象进行检验并封装
    FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException;
//对返回对象进行封装
    HttpServletResponse getFirewalledResponse(HttpServletResponse response);
}

主要有两个实现类。
在这里插入图片描述

DefaultHttpFirewall是一个检查相对宽容的防火墙,虽然叫做default,但实际上Security默认使用的防火墙不是它。
StrictHttpFirewall 是一个检查比较严格的防火墙,也是Security默认使用的防火墙。

在FilterChainProxy中默认使用的就是StrictHttpFirewall 。会话固定攻击的防御就是在这里实现的。

在它的doFilter方法中,如下

 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
            this.doFilterInternal(request, response, chain);
        } else {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } catch (Exception var11) {
                Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var11);
                Throwable requestRejectedException = this.throwableAnalyzer.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
                if (!(requestRejectedException instanceof RequestRejectedException)) {
                    throw var11;
                }

                this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, (RequestRejectedException)requestRejectedException);
            } finally {
                this.securityContextHolderStrategy.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }

        }
    }

使用了doFilterInternal,这个里面:


 private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  //  firewall 就是StrictHttpFirewall  
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
       
        List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
        if (filters != null && filters.size() != 0) {
            if (logger.isDebugEnabled()) {
                logger.debug(LogMessage.of(() -> {
                    return "Securing " + requestLine(firewallRequest);
                }));
            }

            FilterChain reset = (req, res) -> {
                if (logger.isDebugEnabled()) {
                    logger.debug(LogMessage.of(() -> {
                        return "Secured " + requestLine(firewallRequest);
                    }));
                }

                firewallRequest.reset();
                chain.doFilter(req, res);
            };
            this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.of(() -> {
                    return "No security for " + requestLine(firewallRequest);
                }));
            }

            firewallRequest.reset();
            this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
        }
    }

从这里,我们可以看到firewall的判断时间比过滤器更早。

我们看看firewall做了什么什么事。

相关日志打印开关:

logging.level.org.springframework.security=debug

简单模式DefaultHttpFirewall

这个方法就比较简单了,把请求中的//转成/。同时将请求中分号间隔的提取出来,只保留路径。不建议使用。

public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        FirewalledRequest firewalledRequest = new RequestWrapper(request);
        if (this.isNormalized(firewalledRequest.getServletPath()) && this.isNormalized(firewalledRequest.getPathInfo())) {
            String requestURI = firewalledRequest.getRequestURI();
            if (this.containsInvalidUrlEncodedSlash(requestURI)) {
                throw new RequestRejectedException("The requestURI cannot contain encoded slash. Got " + requestURI);
            } else {
                return firewalledRequest;
            }
        } else {
            String var10002 = firewalledRequest.getServletPath();
            throw new RequestRejectedException("Un-normalized paths are not supported: " + var10002 + (firewalledRequest.getPathInfo() != null ? firewalledRequest.getPathInfo() : ""));
        }
    }

严格模式StrictHttpFirewall

 private void rejectForbiddenHttpMethod(HttpServletRequest request) {
 //支持的请求方式
        if (this.allowedHttpMethods != ALLOW_ANY_HTTP_METHOD) {
            if (!this.allowedHttpMethods.contains(request.getMethod())) {
                String var10002 = request.getMethod();
                throw new RequestRejectedException("The request was rejected because the HTTP method \"" + var10002 + "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);
            }
        }
    }

默认支持如下的请求方式


private static Set<String> createDefaultAllowedHttpMethods() {
        Set<String> result = new HashSet();
        result.add(HttpMethod.DELETE.name());
        result.add(HttpMethod.GET.name());
        result.add(HttpMethod.HEAD.name());
        result.add(HttpMethod.OPTIONS.name());
        result.add(HttpMethod.PATCH.name());
        result.add(HttpMethod.POST.name());
        result.add(HttpMethod.PUT.name());
        return result;
    }

让他只能使用post或者get访问,只需要这样配置:


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

允许所有请求方式,则如下

 @Bean
    HttpFirewall firewall(){
        StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
//        strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("POST","GET"));
        //允许所有请求
        strictHttpFirewall.setUnsafeAllowAnyHttpMethod(true);
        return strictHttpFirewall;
    }

对地址中的非法字符进行校验

private void rejectedBlocklistedUrls(HttpServletRequest request) {
        Iterator var2 = this.encodedUrlBlocklist.iterator();

        String forbidden;
        do {
            if (!var2.hasNext()) {
                var2 = this.decodedUrlBlocklist.iterator();

                do {
                    if (!var2.hasNext()) {
                        return;
                    }

                    forbidden = (String)var2.next();
                } while(!decodedUrlContains(request, forbidden));

                throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
            }

            forbidden = (String)var2.next();
        } while(!encodedUrlContains(request, forbidden));

        throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
    }

常用的一些配置。

 @Bean
    HttpFirewall firewall(){
        StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
//        strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("POST","GET"));
        //允许所有请求
        strictHttpFirewall.setUnsafeAllowAnyHttpMethod(true);
       //是否允许分号
        strictHttpFirewall.setAllowSemicolon(true);

        //允许访问的地址
        strictHttpFirewall.setAllowedHostnames(h -> h.equals("localhost"));
        return strictHttpFirewall;
    }

检查请求是否规范

  private static boolean isNormalized(HttpServletRequest request) {
        if (!isNormalized(request.getRequestURI())) {
            return false;
        } else if (!isNormalized(request.getContextPath())) {
            return false;
        } else if (!isNormalized(request.getServletPath())) {
            return false;
        } else {
            return isNormalized(request.getPathInfo());
        }
    }
  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 是一个开源的安全框架,用于保护基于Spring框架构建的应用程序。MyBatis-Plus 是基于 MyBatis 的增强工具,用于简化 MyBatis 操作和提高开发效率。 将 Spring Security 与 MyBatis-Plus 整合主要包括以下几个步骤: 1. 首先,在 Spring Boot 项目的 pom.xml 文件中添加 Spring Security 和 MyBatis-Plus 的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>latest version</version> </dependency> ``` 2. 接下来,在项目的配置文件 application.properties 或 application.yml 中配置数据库连接和 MyBatis-Plus 的相关配置。 3. 创建一个用户实体类和对应的 Mapper 接口,使用 MyBatis-Plus 提供的注解和方法完成数据访问操作。 4. 创建一个自定义的 UserDetailsService 实现类,用于从数据库中获取用户信息,实现 loadUserByUsername 方法。 5. 创建一个自定义的 PasswordEncoder 实现类,用于加密和校验用户密码。 6. 配置 Spring Security,创建一个继承自 WebSecurityConfigurerAdapter 的类,并重写 configure 方法,设置用户认证规则、登录配置和访问控制规则。 7. 在 Spring Boot 启动类上使用 @MapperScan 注解,扫描 Mapper 接口。 通过以上步骤,就可以实现 Spring Security 与 MyBatis-Plus 的整合。在实际应用中,还可以根据具体需求进行一些其他配置和扩展,例如添加角色权限控制、自定义登录页面和处理逻辑等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值