# 建筑

本节讨论 Spring 基于 Servlet 的应用程序中的 Spring 安全性的高级体系结构。我们在参考的[认证](authentication/index.html#servlet-authentication)、[授权](authorization/index.html#servlet-authorization)、[保护免受剥削](exploits/index.html#servlet-exploits)部分中建立了这种高层次的理解。

## 《`Filter`s》评介

Spring 安全性的 Servlet 支持是基于 Servlet `Filter`s 的，因此通常首先查看`Filter`s 的作用是有帮助的。下图显示了单个 HTTP 请求的处理程序的典型分层。

![滤清链](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/filterchain.png)

图 1。滤清链

客户机向应用程序发送一个请求，容器创建一个`FilterChain`，其中包含`Filter`s 和`Servlet`，它们应该基于请求 URI 的路径来处理`HttpServletRequest`。在 Spring MVC 应用程序中，`Servlet`是[`DispatcherServlet`]（https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/web.html#mvc- Servlet）的一个实例。最多一个`Servlet`可以处理单个`HttpServletRequest`和`HttpServletResponse`。但是，可以使用多个`Filter`来：

* 阻止调用下游`Filter`s 或`Servlet`。在这种情况下，`Filter`通常会写`HttpServletResponse`。

* 修改由下游`Filter`s 和`Servlet`使用的`HttpServletRequest`或`HttpServletResponse`

`Filter`的幂来自传递到它的`FilterChain`。

例 1。`FilterChain`用法示例

爪哇

```
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
}
```

Kotlin

```
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // 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`s 和`Servlet`，因此每次调用`Filter`的顺序是极其重要的。

## 委托过滤代理

Spring 提供了名为[`Filter`]（https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/filter/delegatingfilterproxy.html）的`Filter`实现，该实现允许在 Servlet 容器的生命周期和 Spring 的`ApplicationContext`之间架桥。 Servlet 容器允许使用自己的标准注册`Filter`s，但它不知道 Spring 定义的 bean。`DelegatingFilterProxy`可以通过标准 Servlet 容器机制注册，但将所有工作委托给实现`Filter`的 Spring  Bean。

下面是一张`DelegatingFilterProxy`如何与[`Filter`s 和`FilterChain`]（# Servlet-filters-review）相匹配的图片。

![委托过滤代理](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/delegatingfilterproxy.png)

图 2。委托过滤代理

`DelegatingFilterProxy`从`ApplicationContext`中查找*Bean Filter<sub>0</sub>*，然后调用*Bean Filter<sub>0</sub>*。下面可以看到`DelegatingFilterProxy`的伪代码。

例 2。`DelegatingFilterProxy`伪代码

爪哇

```
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// Lazily get Filter that was registered as a Spring Bean
	// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
	Filter delegate = getFilterBean(someBeanName);
	// delegate work to the Spring Bean
	delegate.doFilter(request, response);
}
```

Kotlin

```
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
	// Lazily get Filter that was registered as a Spring Bean
	// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
	val delegate: Filter = getFilterBean(someBeanName)
	// delegate work to the Spring Bean
	delegate.doFilter(request, response)
}
```

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

## FilterchainProxy

Spring Security 的 Servlet 支持包含在`FilterChainProxy`中。`FilterChainProxy`是 Spring Security 提供的一种特殊的`Filter`，它允许通过[`证券过滤链`]（# Servlet-SecurityFilterchain）将许多`Filter`实例委托给多个实例。由于`FilterChainProxy`是 Bean，因此它通常被包装在[委托过滤代理](#servlet-delegatingfilterproxy)中。

![FilterchainProxy ](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/filterchainproxy.png)

图 3。FilterchainProxy

## SecurityFilterChain

[`SecurityFilterChain`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/securityfilterchain.html）被[FilterchainProxy ](#servlet-filterchainproxy)用于确定应该为此请求调用哪个 Spring security`Filter`s。

![证券过滤链](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/securityfilterchain.png)

图 4。证券过滤链

[安全过滤器](#servlet-security-filters)中的`SecurityFilterChain`通常是 bean，但它们是用`FilterChainProxy`而不是[委托过滤代理](#servlet-delegatingfilterproxy)注册的。`FilterChainProxy`提供了直接向 Servlet 容器或[委托过滤代理](#servlet-delegatingfilterproxy)注册的许多优点。首先，它为 Spring Security 的 Servlet 支持提供了一个起点。因此，如果你试图对 Spring Security 的 Servlet 支持进行故障排除，那么在`FilterChainProxy`中添加一个调试点是一个很好的起点。

其次，由于`FilterChainProxy`是 Spring 安全性使用的核心，因此它可以执行不被视为可选的任务。例如，它清除`SecurityContext`以避免内存泄漏。它还应用 Spring Security 的[`HttpFirewall`]（exploits/firewall.html# Servlet-HttpFirewall）来保护应用程序免受某些类型的攻击。

此外，它在确定何时应该调用`SecurityFilterChain`时提供了更大的灵活性。在 Servlet 容器中，`Filter`s 仅基于 URL 被调用。但是，`FilterChainProxy`可以通过利用`RequestMatcher`接口，基于`HttpServletRequest`中的任何内容来确定调用。

事实上，`FilterChainProxy`可以用来确定应该使用哪些`SecurityFilterChain`。这允许为应用程序的不同*切片*提供完全独立的配置。

![多证券过滤链](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/multi-securityfilterchain.png)

图 5。多重证券过滤链

在[多重证券过滤链](#servlet-multi-securityfilterchain-figure)图中，`FilterChainProxy`决定应该使用哪个`SecurityFilterChain`。只调用第一个匹配的`SecurityFilterChain`。如果请求`/api/messages/`的 URL，它将首先匹配`SecurityFilterChain<sub>0</sub>`的`/api/**`的模式，因此只有`SecurityFilterChain<sub>0</sub>`将被调用，即使它也匹配`SecurityFilterChain<sub>n</sub>`。如果请求一个`/messages/`的 URL，它将不匹配`SecurityFilterChain<sub>0</sub>`的`/api/**`的模式，因此`FilterChainProxy`将继续尝试每个`SecurityFilterChain`。假设没有其他的，`SecurityFilterChain`实例匹配`SecurityFilterChain<sub>n</sub>`将被调用。

请注意，`SecurityFilterChain<sub>0</sub>`只配置了三个安全`Filter`实例。但是，`SecurityFilterChain<sub>n</sub>`配置了四个`Filter`的安全性。需要注意的是，每个`SecurityFilterChain`都可以是唯一的，并且可以单独配置。实际上，如果应用程序希望 Spring 安全性忽略某些请求，则`SecurityFilterChain`可能具有零安全性`Filter`s。

## 安全过滤器

使用[证券过滤链](#servlet-securityfilterchain)API 将安全过滤器插入[FilterchainProxy ](#servlet-filterchainproxy)中。[`Filter`]（# Servlet-过滤器-评审）的顺序很重要。通常不需要知道 Spring 证券的`Filter`s 的排序。

下面是 Spring 安全过滤器排序的综合列表：

* 通道处理滤波器

* WebAsyncManagerIntegrationFilter

* SecurityContextPersistenceFilter

* HeaderWriterFilter

* Corsfilter

* Csrffilter

* LogoutFilter

* OAuth2AuthorizationRequestReDirectFilter

* SAML2WebssoAuthenticationRequestFilter

* X509 身份验证过滤器

* 抽象预先验证的处理过滤器

* CasauthenticationFilter

* OAuth2 登录验证过滤器

* SAML2WebssoAuthenticationFilter

* [`UsernamePasswordAuthenticationFilter`]（身份验证/密码/form.html# Servlet-身份验证-usernamepasswordauthenticationfilter）

* openidauthenticationfilter

* DefaultLoginPageGeneratingFilter

* DefaultLogOutpageGeneratingFilter

* ConcurrentSessionFilter

* [`DigestAuthenticationFilter`]（authentication/passwords/digest.html# Servlet-authentication-digest）

* BeareRtoKenAuthenticationFilter

* [`BasicAuthenticationFilter`]（authentication/passwords/basic.html# Servlet-authentication-basic）

* RequestCacheAwareFilter

* SecurityContextholderAwareRequestFilter

* JaasapiIntegrationfilter

* RememerMeAuthenticationFilter

* 匿名身份验证过滤器

* OAuth2AuthorizationCodeGrantFilter

* SessionManagementFilter

* [`ExceptionTranslationFilter`]（# Servlet-ExceptionTranslationFilter）

* [`FilterSecurityInterceptor`]（授权/authorization-requests.html# Servlet-authorization-filtersecurityinterceptor）

* 切换 chuserfilter

## 处理安全异常

[`ExceptionTranslationFilter`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/access/excess/exceptiontranslationfilter.html）允许将[`AccessDeniedException`]（https://DOCS. Spring.io/ Spring.io/ Spring-cesecurity/site/DOCS/5.6.2/api/api/org/accessframework/secframework/securance/securance/securation/acception/acc

`ExceptionTranslationFilter`作为[安全过滤器](#servlet-security-filters)中的一个插入到[FilterchainProxy ](#servlet-filterchainproxy)中。

![ExceptionTranslationFilter ](https://docs.spring.io/spring-security/reference/_images/servlet/architecture/exceptiontranslationfilter.png)

* ![number 1](https://docs.spring.io/spring-security/reference/_images/icons/number_1.png)首先，`ExceptionTranslationFilter`调用`FilterChain.doFilter(request, response)`来调用应用程序的其余部分。

* ![number 2](https://docs.spring.io/spring-security/reference/_images/icons/number_2.png)如果用户没有经过身份验证，或者它是`AuthenticationException`，那么*启动身份验证*。

  * [SecurityContextholder ](authentication/architecture.html#servlet-authentication-securitycontextholder)已清除

  * `HttpServletRequest`保存在[`RequestCache`]（https://DOCS. Spring.io/ Spring-security/site/DOCS/5.6.2/api/org/springframework/security/web/savedrequest/requestcache.html）中。当用户成功进行身份验证时，将使用`RequestCache`重播原始请求。

  * `AuthenticationEntryPoint`用于从客户机请求凭据。例如，它可能重定向到一个登录页面，或者发送一个`WWW-Authenticate`头。

* ![number 3](https://docs.spring.io/spring-security/reference/_images/icons/number_3.png)否则如果是`AccessDeniedException`，则*访问被拒绝*。调用`AccessDeniedHandler`来处理拒绝访问。

|   |如果应用程序不抛出`AccessDeniedException`或`AuthenticationException`，则`ExceptionTranslationFilter`不执行任何操作。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------|

`ExceptionTranslationFilter`的伪代码如下所示：

ExceptionTranslationFilter 伪码

```
try {
	filterChain.doFilter(request, response); (1)
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication(); (2)
	} else {
		accessDenied(); (3)
	}
}
```

|**1**|你可以从[对`Filter`s 的回顾]（# Servlet-filters-review）中回忆起，调用`FilterChain.doFilter(request, response)`相当于调用应用程序的其余部分。<br/>这意味着，如果应用程序的另一部分，（即[`FilterSecurityInterceptor`]（Authorization/authorization-requests.html# Servlet-authorization-filterSecurityInterceptor）或方法安全性）抛出一个`AuthenticationException`或`AccessDeniedException`将在此捕获并处理。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|如果用户没有经过身份验证，或者它是`AuthenticationException`，那么*启动身份验证*。|
|**3**|否则，*访问被拒绝*|

[开始](getting-started.html)[认证](authentication/index.html)

