首页 > 其他分享 >Architecture

Architecture

时间:2024-09-05 15:27:01浏览次数:8  
标签:web 调用 Spring Filter Architecture 过滤器 security

Architecture

本节讨论 Spring Security 在基于 Servlet 的应用程序中的高级体系结构。我们在参考的 Authentication, Authorization, and Protection against Exploits 部分中建立了这种高级理解。

A Review of Filters

Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先查看过滤器的角色会很有帮助。下图显示了单个 HTTP 请求的处理程序的典型分层。

客户端向应用程序发送请求,容器创建一个 FilterChain,其中包含 Filter 实例和应根据请求 URI 的路径处理 HttpServletRequest 的 Servlet。在 Spring MVC 应用程序中,Servlet 是 DispatcherServlet 的实例。一个 Servlet 最多可以处理单个 HttpServletRequest 和 HttpServletResponse。但是,可以使用多个 Filter 来:

  • Prevent downstream Filter instances or the Servlet from being invoked. In this case, the Filter typically writes the HttpServletResponse.
    • 防止下游 Filter 实例或 Servlet 被调用。在这种情况下,Filter 通常会写入 HttpServletResponse。
  • Modify the HttpServletRequest or HttpServletResponse used by the downstream Filter instances and the Servlet.
    • 修改下游 Filter 实例和 Servlet 使用的 HttpServletRequest 或 HttpServletResponse。

Filter 的强大功能来自传递给它的 FilterChain。

FilterChain Usage Example

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

由于 Filter 仅影响下游 Filter 实例和 Servlet,因此调用每个 Filter 的顺序非常重要。

DelegatingFilterProxy(委派筛选器代理器)

Spring 提供了一个名为 DelegatingFilterProxy 的Filter实现,它允许在 Servlet 容器的生命周期和 Spring 的ApplicationContext之间进行桥接。Servlet 容器允许使用自己的标准注册 Filter 实例,但它不知道 Spring 定义的 Bean。你可以通过标准的 Servlet 容器机制注册DelegatingFilterProxy,但将所有工作委托给实现Filter的 Spring Bean。这是 DelegatingFilterProxy 如何适应 Filter 实例和 FilterChain 的图片。

 

DelegatingFilterProxy从ApplicationContext中查找 Bean Filter0,然后调用 Bean Filter0。下面的清单显示了DelegatingFilterProxy的伪代码:

DelegatingFilterProxy Pseudo Code

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    Filter delegate = getFilterBean(someBeanName);①
    delegate.doFilter(request, response);②
}

 ① 延迟获取已注册为 Spring Bean 的 Filter。对于 DelegatingFilterProxy 中的示例,委托是 Bean Filter0 的实例。

② 将工作委托给 Spring Bean。

DelegatingFilterProxy的另一个好处是它允许延迟查找Filter bean 实例。这一点很重要,因为容器需要先注册 Filter 实例,然后才能启动容器。但是, Spring 通常使用ContextLoaderListener来加载 Spring Bean,直到需要注册Filter实例之后才会完成。

FilterChainProxy

Spring Security 的 Servlet 支持包含在FilterChainProxy中。FilterChainProxy是 Spring Security 提供的特殊Filter,它允许通过SecurityFilterChain委托给多个Filter实例。由于FilterChainProxy是一个 Bean,因此它通常包装在DelegatingFilterProxy中。下图显示了 FilterChainProxy 的角色。

 

SecurityFilterChain

FilterChainProxy 用 SecurityFilterChain 来确定应该为当前请求调用哪些 Spring Security Filter 实例。下图显示了 SecurityFilterChain 的角色。

SecurityFilterChain 中的安全过滤器通常是 Bean,但它们是使用 FilterChainProxy 而不是 DelegatingFilterProxy 注册的。FilterChainProxy为直接向 Servlet 容器或DelegatingFilterProxy注册提供了许多优势。首先,它为 Spring Security 的所有 Servlet 支持提供了一个起点。因此,如果你尝试对 Spring Security 的 Servlet 支持进行故障排除,那么在FilterChainProxy中添加一个调试点是一个很好的起点。

其次,由于FilterChainProxy是 Spring Security 使用的核心,因此它可以执行不被视为可选的任务。例如,它会清除 SecurityContext 以避免内存泄漏。它还应用 Spring Security 的 HttpFirewall 来保护应用程序免受某些类型的攻击。

此外,它在确定何时应调用 SecurityFilterChain 方面提供了更大的灵活性。在 Servlet 容器中,仅基于 URL 调用 Filter 实例。但是,FilterChainProxy可以通过使用RequestMatcher接口根据HttpServletRequest中的任何内容来确定调用。

下图显示了多个 SecurityFilterChain 实例:

 

在多个 SecurityFilterChain 图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。仅调用匹配的第一个 SecurityFilterChain。如果请求 /api/messages/ 的 URL,则它首先匹配 /api/** 的 SecurityFilterChain0 模式,因此仅调用 SecurityFilterChain0,即使它也匹配 SecurityFilterChainn。如果请求的 URL 为 /messages/,则该 URL 与 /api/** 的 SecurityFilterChain0 模式不匹配,因此 FilterChainProxy 将继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例匹配,则调用 SecurityFilterChainn。

请注意,SecurityFilterChain0 只配置了三个安全 Filter 实例。但是,SecurityFilterChainn 配置了四个安全筛选器实例。请务必注意,每个 SecurityFilterChain 可以是唯一的,并且可以单独配置。实际上,如果应用程序希望 Spring Security 忽略某些请求,则SecurityFilterChain可能具有零安全Filter实例。

Security Filters

安全筛选器使用 SecurityFilterChain API 插入到 FilterChainProxy 中。这些过滤器可用于多种不同的目的,例如身份验证、授权、漏洞利用保护等。过滤器按特定顺序执行,以保证在正确的时间调用它们,例如,执行身份验证的 Filter 应在执行授权的 Filter 之前调用。通常不需要知道 Spring Security 的过滤器的顺序。但是,有时了解顺序是有益的,如果您想了解它们,您可以查看 FilterOrderRegistration 代码。

为了举例说明上述段落,让我们考虑以下安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults());
        return http.build();
    }

}

上述配置将导致以下 Filter 排序:

FilterAdded by

CsrfFilter

HttpSecurity#csrf

UsernamePasswordAuthenticationFilter

HttpSecurity#formLogin

BasicAuthenticationFilter

HttpSecurity#httpBasic

AuthorizationFilter

HttpSecurity#authorizeHttpRequests

  1. 首先,调用 CsrfFilter 来防止 CSRF 攻击。
  2. 其次,调用身份验证筛选器来验证请求。
  3. 第三,调用 AuthorizationFilter 来授权请求。

Printing the Security Filters

通常,查看为特定请求调用的安全过滤器列表非常有用。例如,您希望确保已添加的过滤器位于安全过滤器列表中。

筛选器列表在应用程序启动时以 INFO 级别打印,因此您可以在控制台输出上看到类似于以下内容的内容,例如:

 1 2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
 2 org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
 3 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
 4 org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
 5 org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
 6 org.springframework.security.web.csrf.CsrfFilter@c29fe36,
 7 org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
 8 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
 9 org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
10 org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
12 org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
13 org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
14 org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
15 org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
16 org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

这将很好地了解为每个过滤器链配置的安全过滤器。

但这还不是全部,您还可以将应用程序配置为打印每个请求的每个单独筛选条件的调用。这有助于查看是否为特定请求调用了已添加的筛选器,或者检查异常的来源。为此,您可以将应用程序配置为记录安全事件。

Adding a Custom Filter to the Filter Chain

大多数情况下,默认安全筛选器足以为您的应用程序提供安全性。但是,有时您可能希望将自定义 Filter 添加到安全筛选器链中。

例如,假设您要添加一个 Filter 来获取租户 ID 标头,并检查当前用户是否有权访问该租户。前面的描述已经给了我们在哪里添加过滤器的线索,因为我们需要知道当前用户,所以我们需要在认证过滤器之后添加它。

First, let’s create the Filter:

import java.io.IOException;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String tenantId = request.getHeader("X-Tenant-Id"); (1)
        boolean hasAccess = isUserAllowed(tenantId); (2)
        if (hasAccess) {
            filterChain.doFilter(request, response); (3)
            return;
        }
        throw new AccessDeniedException("Access denied"); (4)
    }

}

上面的示例代码执行以下操作:

  1. 从请求标头中获取租户 ID。
  2. 检查当前用户是否有权访问租户 ID。
  3. 如果用户具有访问权限,则调用链中的其余筛选器。
  4. 如果用户没有访问权限,则引发 AccessDeniedException。
Tip

你可以从 OncePerRequestFilter 扩展,而不是实现 Filter,OncePerRequestFilter 是每个请求仅调用一次的过滤器的基类,并提供带有 HttpServletRequest 和 HttpServletResponse 参数的 doFilterInternal 方法。

现在,我们需要将过滤器添加到安全过滤器链中。

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
    return http.build();
}

① 使用 HttpSecurity#addFilterBefore 在 AuthorizationFilter 之前添加 TenantFilter。

通过在 AuthorizationFilter 之前添加过滤器,我们可以确保在身份验证过滤器之后调用 TenantFilter。您还可以使用 HttpSecurity#addFilterAfter 在特定过滤器之后添加过滤器,或使用 HttpSecurity#addFilterAt 在过滤器链中的特定过滤器位置添加过滤器。

就是这样,现在 TenantFilter 将在过滤器链中调用,并检查当前用户是否有权访问租户 ID。

将过滤器声明为 Spring bean 时要小心,无论是用 @Component 注释它,还是在配置中将其声明为 bean,因为 Spring Boot 会自动将其注册到嵌入式容器中。这可能会导致过滤器被调用两次,一次由容器调用,一次由 Spring Security 调用,并且顺序不同。

例如,如果你仍然想将过滤器声明为 Spring Bean 以利用依赖关系注入,并避免重复调用,你可以通过声明FilterRegistrationBean Bean 并将其enabled属性设置为false来告诉 Spring Boot 不要在容器中注册它:

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}

Handling Security Exceptions

ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。

ExceptionTranslationFilter 作为安全筛选器之一插入到 FilterChainProxy 中。

下图显示了 ExceptionTranslationFilter 与其他组件的关系:

  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其余部分。
  2. 如果用户未通过身份验证或为 AuthenticationException,则 Start Authentication (启动身份验证)。SecurityContextHolder被清除。HttpServletRequest 将被保存,以便在身份验证成功后可以使用它来重放原始请求。AuthenticationEntryPoint 用于从客户端请求凭据。例如,它可能会重定向到登录页面或发送 WWW-Authenticate 标头。
  3. 否则,如果它是 AccessDeniedException,则为 Access Denied。调用 AccessDeniedHandler 来处理被拒绝的访问。
Tip 

如果应用程序没有抛出AccessDeniedException或AuthenticationException,则ExceptionTranslationFilter不执行任何操作。

ExceptionTranslationFilter 的伪代码如下所示:

ExceptionTranslationFilter pseudocode

try {
    filterChain.doFilter(request, response);①
} catch (AccessDeniedException | AuthenticationException ex) {
    if (!authenticated || ex instanceof AuthenticationException) {
        startAuthentication();②
    } else {
        accessDenied();③
    }
}
  1. 如筛选器回顾 中所述,调用 FilterChain.doFilter(request, response) 等效于调用应用程序的其余部分。这意味着,如果应用程序的另一部分(FilterSecurityInterceptor或方法安全性)抛出AuthenticationException或AccessDeniedException,则会在此处捕获并处理它。
  2. 如果用户未经过身份验证或为 AuthenticationException,则启动 Authentication。
  3. 否则,Access Denied (访问被拒绝)

Saving Requests Between Authentication

如处理安全异常中所述,当请求没有身份验证并且针对需要身份验证的资源时,需要保存请求,以便经过身份验证的资源在身份验证成功后重新请求。在 Spring Security 中,这是通过使用RequestCache实现保存HttpServletRequest来完成的。

RequestCache

HttpServletRequest 保存在 RequestCache 中。当用户成功进行身份验证时,将使用 RequestCache 重播原始请求。RequestCacheAwareFilter在用户进行身份验证后使用RequestCache来获取保存的HttpServletRequest,而ExceptionTranslationFilter在检测到AuthenticationException后,在将用户重定向到登录端点之前,使用RequestCache来保存HttpServletRequest。

默认情况下,使用 HttpSessionRequestCache。下面的代码演示了如何自定义 RequestCache 实现,如果存在名为 continue 的参数,则用于检查 HttpSession 中是否有已保存的请求。

RequestCache Only Checks for Saved Requests if continue Parameter Present

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
    requestCache.setMatchingRequestParameterName("continue");
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(requestCache)
        );
    return http.build();
}

Prevent the Request From Being Saved

出于多种原因,您可能希望不在会话中存储用户的未经身份验证的请求。您可能希望将该存储卸载到用户的浏览器上或将其存储在数据库中。或者您可能希望关闭此功能,因为您总是希望将用户重定向到主页,而不是他们在登录前尝试访问的页面。

为此,您可以使用 NullRequestCache 实现。

Prevent the Request From Being Saved

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

RequestCacheAwareFilter

RequestCacheAwareFilter使用RequestCache重播原始请求。

Logging

Spring Security 在 DEBUG 和 TRACE 级别提供了所有与安全相关的事件的全面日志记录。这在调试应用程序时非常有用,因为为了安全措施, Spring Security 不会在响应正文中添加请求被拒绝原因的任何详细信息。如果您遇到 401 或 403 错误,您很可能会找到一条日志消息,帮助您了解发生了什么。

让我们考虑一个例子,用户尝试向启用了 CSRF 保护的资源发出 POST 请求,但没有 CSRF 令牌。如果没有日志,用户将看到 403 错误,并且没有解释请求被拒绝的原因。但是,如果为 Spring Security 启用日志记录,则会看到如下日志消息:

1 2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /hello
2 2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/15)
3 2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/15)
4 2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/15)
5 2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/15)
6 2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Invoking CsrfFilter (5/15)
7 2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://localhost:8080/hello
8 2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl   : Responding with 403 status code
9 2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match request to [Is Secure]

很明显,CSRF 令牌丢失了,这就是请求被拒绝的原因。

要将应用程序配置为记录所有安全事件,可以将以下内容添加到应用程序中:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- ... -->
    </appender>
    <!-- ... -->
    <logger name="org.springframework.security" level="trace" additivity="false">
        <appender-ref ref="Console" />
    </logger>
</configuration>

标签:web,调用,Spring,Filter,Architecture,过滤器,security
From: https://www.cnblogs.com/neverheartache/p/18398423

相关文章

  • Lecture 02 Layered Architecture of Game Engine
    Lecture02LayeredArchitectureofGameEngine渲染只是游戏引擎中不大的一部分ToolLayer工具层这部分不是实时的,所有可以允许多种实现方法(C++/C#开发等等)DCCDigitalContentCreation将不同文件导入成Assets·FunctionLayer功能层每个tick依次做完所有内......
  • <architecture>-linux-gnu-gcc
    ./configure--enable-win64yuminstalllibstdc++-*.i686yuminstallglibc-*.i686yuminstalllibgcc.i686sudodpkg--add-architecturei386sudoaptupdate安装相应的32位库,库名后面加上:i386,比如libc6:i386,libstdc++6:i386,libncurses5:i386sudodpkg--add-architec......
  • MySQL Architecture And Engine
    Author:ACatSmilingSince:2024-07-22SELECT的执行过程SELECT的查询结构方式一:SELECT...,...,...FROM...,...,...WHERE多表的连接条件AND不包含组函数的过滤条件GROUPBY...,...HAVING包含组函数的过滤条件ORDERBY...ASC/DESCLIMIT...,...;方式二......
  • transformer model architecture
    transformermodelarchitecturehttps://www.datacamp.com/tutorial/how-transformers-work 动手写https://www.datacamp.com/tutorial/building-a-transformer-with-py-torch Attentionhttps://www.cnblogs.com/jins-note/p/13056604.html人类的视觉注意力从注意力......
  • PerFedRLNAS: One-for-All Personalized Federated Neural Architecture Search-_AAAI
    PerFedRLNAS:One-for-AllPersonalizedFederatedNeuralArchitectureSearch-AAAI'24-PerFedRLNAS背景与挑战:介绍个性化学习来解决设备异构和数据异构的问题。现有工作无法充分回答下面的两个问题:1.为什么个性化模型可以解决联邦的异构问题2.是否有标准和自动的方法来决......
  • FedNAS: Federated Deep Learning via Neural Architecture Search-_BaseLine-FedNAS
    背景与挑战:介绍FL,引出数据异构问题和数据不可见性,因此需要大量的人力来定制更好的模型架构,因为设备异构性,边缘设备需要更高的计算负担和通信成本。介绍解决数据异构的相关工作,指出这些工作需要强大的先验假设。预定义的模型不一定是最优的贡献:1.提出FedNAS方法,在边缘设备之间......
  • FINCH: Enhancing Federated Learning With Hierarchical Neural Architecture Search
    背景与挑战:介绍FL联邦学习,指出两个联邦学习的缺点::::danger1.预定义的架构容易使模型训练陷入局部次优解,导致训练性能低下2.开发一个足够精确和小的模型来部署在客户端是很复杂的,这需要在迭代的试错过程中付出大量的人力:::(手动设计更高效的体系结构在很大程度上依赖于人类......
  • Peaches: Personalized Federated Learning with Neural Architecture Search in Edge
    背景:介绍联邦学习,参数服务器和workers之间的关系挑战:1.预定义模型:太大的架构可能会导致过度拟合问题和workers不必要的计算开销,而太小的架构可能会导致低训练性能2.数据不可访问:数据不可访问导致不能设计出真正高效的架构在边缘计算中使用FL。需要考虑三种挑战:1.异构数据2......
  • Real-Time Federated Evolutionary Neural Architecture Search-_TEC'22(B)-RT-FedEvo
    背景:1.介绍FL2.介绍NAS(讨论范围限制在CNN)宏搜索空间覆盖整个CNN模型,例如,隐藏层的数量n、操作类型(例如,卷积)和快捷连接的链接方法微观搜索空间仅覆盖整个模型结构中重复的基序或细胞。并且这些单元在复杂的多分支操作中构建介绍RT(强化学习)在NAS中的运用,因为RT要模拟采样,......
  • Towards Accurate and Robust Architectures via Neural Architecture Search
    基于网络架构搜索的准确性与鲁棒性结构研究论文链接:https://arxiv.org/abs/2405.05502项目链接:未开源Abstract为了保护深度神经网络免受对抗性攻击,对抗性训练因其有效性而受到越来越多的关注。然而,对抗训练的准确性和鲁棒性受到体系结构的限制,因为对抗训练通过调整隶属......