网站什么时候备案,西安找公司建网站,德阳建设公司网站,怎么做网页代理目录
一、Filter#xff08;过滤器#xff09;回顾
二、DelegatingFilterProxy
三、FilterChainProxy
四、SecurityFilterChain
五、Security Filter
六、打印出 Security Filter
七、添加自定义 Filter 到 Filter Chain
八、处理 Security 异常
九、保存认证之间的…
目录
一、Filter过滤器回顾
二、DelegatingFilterProxy
三、FilterChainProxy
四、SecurityFilterChain
五、Security Filter
六、打印出 Security Filter
七、添加自定义 Filter 到 Filter Chain
八、处理 Security 异常
九、保存认证之间的请求
十、RequestCache 本站(springdoc.cn)中的内容来源于 spring.io 原始版权归属于 spring.io。由 springdoc.cn 进行翻译整理。可供个人学习、研究未经许可不得进行任何转载、商用或与之相关的行为。 商标声明Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。
本节讨论了 Spring Security 在基于 Servlet 的应用程序中的高级架构。我们在参考资料中的 认证Authentication、授权Authorization 和 防止漏洞 部分建立了这种高层次的理解。
一、Filter过滤器回顾
Spring Security 对 Servlet 的支持是基于Servlet过滤器的所以先看一下过滤器的一般作用是很有帮助的。下图显示了单个HTTP请求的处理程序的典型分层。 Figure 1. FilterChain
客户端向应用程序发送一个请求容器创建一个 FilterChain其中包含 Filter 实例和 Servlet应该根据请求URI的路径来处理 HttpServletRequest。在Spring MVC应用程序中Servlet是 DispatcherServlet 的一个实例。一个 Servlet 最多可以处理一个 HttpServletRequest 和 HttpServletResponse。然而可以使用多个 Filter 来完成如下工作。
防止下游的 Filter 实例或 Servlet 被调用。在这种情况下Filter 通常会使用 HttpServletResponse 对客户端写入响应。修改下游的 Filter 实例和 Servlet 所使用的 HttpServletRequest 或 HttpServletResponse。
过滤器的力量来自于传入它的 FilterChain。
FilterChain Usage Example
Java
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {// do something before the rest of the applicationchain.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 的图片。 Figure 2. DelegatingFilterProxy
DelegatingFilterProxy 从 ApplicationContext 查找 Bean Filter0然后调用 Bean Filter0。下面的列表显示了 DelegatingFilterProxy 的伪代码。
DelegatingFilterProxy Pseudo Code
Java
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {Filter delegate getFilterBean(someBeanName); delegate.doFilter(request, response);
} 延迟地获取被注册为Spring Bean的 Filter。 对于 DelegatingFilterProxy 中的例子delegate 是 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 的作用。 Figure 3. FilterChainProxy
四、SecurityFilterChain
SecurityFilterChain 被 FilterChainProxy 用来确定当前请求应该调用哪些 Spring Security Filter 实例。
下图显示了 SecurityFilterChain 的作用。 Figure 4. SecurityFilterChain
SecurityFilterChain 中的 Security Filter 通常是Bean但它们是用 FilterChainProxy 而不是 DelegatingFilterProxy 注册的。与直接向Servlet容器或 DelegatingFilterProxy 注册相比FilterChainProxy 有很多优势。首先它为 Spring Security 的所有 Servlet 支持提供了一个起点。由于这个原因如果你试图对 Spring Security 的 Servlet 支持进行故障诊断在 FilterChainProxy 中添加一个调试点是一个很好的开始。
其次由于 FilterChainProxy 是 Spring Security 使用的核心它可以执行一些不被视为可有可无的任务。 例如它清除了 SecurityContext 以避免内存泄漏。它还应用Spring Security的 HttpFirewall 来保护应用程序免受某些类型的攻击。
此外它在确定何时应该调用 SecurityFilterChain 方面提供了更大的灵活性。在Servlet容器中Filter 实例仅基于URL被调用。 然而FilterChainProxy 可以通过使用 RequestMatcher 接口根据 HttpServletRequest 中的任何内容确定调用。
下图显示了多个 SecurityFilterChain 实例。 Figure 5. Multiple SecurityFilterChain
在 Multiple SecurityFilterChain 图中 FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 被调用。如果请求的URL是 /api/messages/它首先与 /api/** 的 SecurityFilterChain0 模式匹配所以只有 SecurityFilterChain0 被调用尽管它也与 SecurityFilterChainn 匹配。如果请求的URL是 /messages/它与 /api/** 的 SecurityFilterChain0 模式不匹配所以 FilterChainProxy 继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例相匹配则调用 SecurityFilterChainn。
请注意SecurityFilterChain0 只配置了三个 security Filter 实例。然而SecurityFilterChainn 却配置了四个 security Filter 实例。值得注意的是每个 SecurityFilterChain 都可以是唯一的并且可以单独配置。事实上如果应用程序希望 Spring Security 忽略某些请求那么一个 SecurityFilterChain 可能会有零个 security Filter 实例。
五、Security Filter
Security Filter 是通过 SecurityFilterChain API 插入 FilterChainProxy 中的。
这些 filter 可以用于许多不同的目的如 认证、 授权、 漏洞保护 等等。filter 是按照特定的顺序执行的以保证它们在正确的时间被调用例如执行认证的 Filter 应该在执行授权的 Filter 之前被调用。一般来说没有必要知道 Spring Security 的 Filter 的顺序。但是有些时候知道顺序是有好处的如果你想知道它们可以查看 FilterOrderRegistration 代码。
为了解释上面这段话让我们考虑以下 security 配置
Java
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic 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 顺序如下 Filter 添加者 CsrfFilter HttpSecurity#csrf UsernamePasswordAuthenticationFilter HttpSecurity#formLogin BasicAuthenticationFilter HttpSecurity#httpBasic AuthorizationFilter HttpSecurity#authorizeHttpRequests
首先调用 CsrfFilter 来防止 CSRF 攻击。其次认证 filter 被调用以认证请求。第三调用 AuthorizationFilter 来授权该请求。 可能还有其他的 Filter 实例没有在上面列出。如果你想看到为某个特定请求调用的 filter 列表你可以 打印出它们。
六、打印出 Security Filter
很多时候看到为某一特定请求调用的 security Filter 的列表是很有用的。例如你想确保 你添加的 filter 在 security filter 的列表中。
filter 列表在应用程序启动时以 INFO 级别打印因此你可以在控制台输出中看到类似下面的内容
2023-06-14T08:55:22.321-03:00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter6f76c2cc,
org.springframework.security.web.csrf.CsrfFilterc29fe36,
org.springframework.security.web.authentication.logout.LogoutFilteref60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter169268a7]
而这将使我们对 每个 filter chain 所配置的 security filter 有一个相当好的了解。
但这还不是全部你还可以配置你的应用程序来打印每个请求的每个 filter 的调用情况。这对于查看你所添加的 filter 是否为某个特定的请求所调用或检查异常的来源是有帮助的。要做到这一点你可以配置你的应用程序来 记录 security event。
七、添加自定义 Filter 到 Filter Chain
大多数情况下默认的 security filter 足以为你的应用程序提供安全。然而有时你可能想在 security filter chain 中添加一个自定义的 filter。
例如假设你想添加一个 Filter获得一个租户 id header 并检查当前用户是否有访问该租户的权限。前面的描述已经给了我们一个添加 filter 的线索因为我们需要知道当前的用户所以我们需要在认证 filter 之后添加它。
首先创建一个 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 {Overridepublic 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); boolean hasAccess isUserAllowed(tenantId); if (hasAccess) {filterChain.doFilter(request, response); return;}throw new AccessDeniedException(Access denied); }}
上面的示例代码做了以下工作 从请求头中获取租户ID。 检查当前用户是否对租户ID有访问权。 如果该用户有访问权那么就调用链中其他的 filter。 如果用户没有权限则抛出一个 AccessDeniedException。 你可以从 OncePerRequestFilter 中继承而不是实现 Filter这是一个基类用于每个请求只调用一次的 filter并提供一个带有 HttpServletRequest 和 HttpServletResponse 参数的 doFilterInternal 方法。
现在我们需要把这个 filter 添加到 security filter chain 中。
Java
Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// ....addFilterBefore(new TenantFilter(), AuthorizationFilter.class); return http.build();
} 使用 HttpSecurity#addFilterBefore 在 AuthorizationFilter 之前添加 TenantFilter。
通过在 AuthorizationFilter 之前添加filter我们确保 TenantFilter 在认证 filter 之后被调用。你也可以使用 HttpSecurity#addFilterAfter 将 filter 添加到某个特定的 filter 之后或者使用 HttpSecurity#addFilterAt 将 filter 添加到 filter chain 中的某个位置。
就这样现在 TenantFilter 将在过 filter chain 中被调用并将检查当前用户是否对租户ID有访问权。
当你把你的 filter 声明为 Spring Bean 时要小心可以用 Component 注解它也可以在配置中把它声明为 Bean因为 Spring Boot 会自动 在嵌入式容器中注册它。这可能会导致 filter 被调用两次一次由容器调用一次由 Spring Security 调用而且顺序不同。
如果你仍然想把你的 filter 声明为 Spring Bean以利用依赖注入避免重复调用你可以通过声明 FilterRegistrationBean Bean 并将其 enabled 属性设置为 false 来告诉 Spring Boot 不要向容器注册它
Bean
public FilterRegistrationBeanTenantFilter tenantFilterRegistration(TenantFilter filter) {FilterRegistrationBeanTenantFilter registration new FilterRegistrationBean(filter);registration.setEnabled(false);return registration;
}
八、处理 Security 异常
ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 翻译成 HTTP 响应。
ExceptionTranslationFilter 作为 Security Filter 之一被插入到 FilterChainProxy 中。
下面的图片显示了 ExceptionTranslationFilter 与其他组件的关系。 首先ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其他部分。如果用户没有被认证或者是一个 AuthenticationException那么就 开始认证。 SecurityContextHolder 被清理掉。HttpServletRequest 被保存起来这样一旦认证成功它就可以用来重放原始请求。AuthenticationEntryPoint 用于请求客户的凭证。例如它可以重定向到一个登录页面或发送一个 WWW-Authenticate 头。否则如果是 AccessDeniedException那么就是 Access Denied。 AccessDeniedHandler 被调用来处理拒绝访问access denied。 如果应用程序没有抛出 AccessDeniedException 或 AuthenticationException那么 ExceptionTranslationFilter 就不会做任何事情。
ExceptionTranslationFilter 的伪代码看起来是这样的。
ExceptionTranslationFilter pseudocode
try {filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {if (!authenticated || ex instanceof AuthenticationException) {startAuthentication(); } else {accessDenied(); }
} 正如 Filter过滤器回顾 中所述调用 FilterChain.doFilter(request, response) 等同于调用应用程序的其他部分。这意味着如果应用程序的另一部分FilterSecurityInterceptor 或 method security抛出一个 AuthenticationException 或 AccessDeniedException它将在这里被捕获和处理。 如果用户没有被认证或者是一个 AuthenticationException则 开始认证。 否则拒绝访问Access Denied
九、保存认证之间的请求
正如在 处理 Security 异常 中所说明的当一个请求没有认证并且是针对需要认证的资源时有必要保存认证资源的请求以便在认证成功后重新请求。在Spring Security中这是通过使用 RequestCache 实现来保存 HttpServletRequest 的。
十、RequestCache
HttpServletRequest 被保存在 RequestCache。当用户成功认证后RequestCache 被用来重放原始请求。RequestCacheAwareFilter 就是使用 RequestCache 来保存 HttpServletRequest 的。
默认情况下使用一个 HttpSessionRequestCache。下面的代码演示了如何定制 RequestCache 的实现如果名为 continue 的参数存在它将用于检查 HttpSession 是否有保存的请求。
Example 1. RequestCache Only Checks for Saved Requests if continue Parameter Present
Java
Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {HttpSessionRequestCache requestCache new HttpSessionRequestCache();requestCache.setMatchingRequestParameterName(continue);http// ....requestCache((cache) - cache.requestCache(requestCache));return http.build();
}
Kotlin
Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {val httpRequestCache HttpSessionRequestCache()httpRequestCache.setMatchingRequestParameterName(continue)http {requestCache {requestCache httpRequestCache}}return http.build()
}
XML
http auto-configtrue!-- ... --request-cache refrequestCache/
/httpb:bean idrequestCache classorg.springframework.security.web.savedrequest.HttpSessionRequestCachep:matchingRequestParameterNamecontinue/ 防止请求被保存
有很多原因你可能想不在 session 中存储用户的未经认证的请求。你可能想把这种存储卸载到用户的浏览器上或者把它存储在数据库中。或者你可能想关闭这个功能因为你总是想把用户重定向到主页而不是他们登录前试图访问的页面。
要做到这一点你可以使用 NullRequestCache 实现.
Java
Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {RequestCache nullRequestCache new NullRequestCache();http// ....requestCache((cache) - cache.requestCache(nullRequestCache));return http.build();
}
Kotlin
Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {val nullRequestCache NullRequestCache()http {requestCache {requestCache nullRequestCache}}return http.build()
}
XML
http auto-configtrue!-- ... --request-cache refnullRequestCache/
/httpb:bean idnullRequestCache classorg.springframework.security.web.savedrequest.NullRequestCache/ RequestCacheAwareFilter
RequestCacheAwareFilter 使用 RequestCache 来保存 HttpServletRequest。 日志
Spring Security 在 DEBUG 和 TRACE 级别提供了对所有 security 相关事件的全面记录。这在调试你的应用程序时非常有用因为出于安全考虑Spring Security 不会在响应体中添加任何关于请求被拒绝的细节。如果你遇到 401 或 403 错误你很可能会找到一条日志信息帮助你了解发生了什么。
让我们考虑一个例子一个用户试图向一个启用了 CSRF保护 的资源发出一个 POST 请求但没有CSRF令牌。在没有日志的情况下用户会看到一个 403 错误没有解释为什么请求被拒绝。然而如果你为 Spring Security 启用了日志你会看到这样的日志信息
2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Securing POST /hello
2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/15)
2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/15)
2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/15)
2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/15)
2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (5/15)
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
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
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令牌缺失这就是请求被拒绝的原因。
要配置你的应用程序来记录所有的 security 事件你可以向你的应用程序添加以下内容
properties
logging.level.org.springframework.securityTRACE
logback.xml
configurationappender nameSTDOUT classch.qos.logback.core.ConsoleAppender!-- ... --/appender!-- ... --logger nameorg.springframework.security leveltrace additivityfalseappender-ref refConsole //logger
/configuration