Spring Security

Spring Security

title:version
5.6.x

Spring Security 是一个提供认证、授权以及一些常见漏洞防护的框架。该框架为 Servlet 应用程序(Spring MVC)和响应式应用程序(Spring WebFlux,本文不表)提供防护,并作为防护 spring 项目的事实标准。

Servlet 应用程序

Spring Security 通过标准的 Servlet 过滤器 集成 Servlet 容器。这表明只要在 Servlet 容器中运行的应用程序都可以使用该框架。具体来说,你不需要在以 Servlet 为容器的应用程序中专门引入 Spring,就可以使用该框架。

在 Spring Boot 项目中使用 Spring Security

新建项目

build.gradle 中输入以下内容:

plugins {  
    id 'org.springframework.boot' version '2.6.7'  
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  
    id 'java'  
}  
  
group = 'com.github.toy'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '17'  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-security'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testImplementation 'org.springframework.security:spring-security-test'  
}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

运行

控制台输出

...
2022-04-30 16:21:28.626  WARN 37347 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: fceb50df-05cf-40e4-a4c5-67d5da55a6b4
...

Spring Boot Auto Configuration

Spring Boot 自动处理以下内容:

  • 启用 Spring Security 的默认配置,它会创建一个 servlet 过滤器 bean,叫做 springSecurityFilterChain。这个 bean 负责应用程序中的所有安全工作(保护应用程序的 URL,验证提交的用户名和密码,重定向登录表单,等等)。
  • 用户名(username)为 user,密码为 console 中生成的随机密码,使用此账号和密码创建 UserDetailsService bean。
  • 通过 Servlet 容器为每个请求注册名为 springSecurityFilterChain过滤器

Spring Boot 配置的内容不多,但是做的很多。功能总结如下:

Spring Security 在 Servlet 应用程序中的顶层架构

我们将基于认证、授权、Protection Against Exploits 来理解这种顶层架构

过滤器

Spring Security 的 Servlet 建立在 Servlet 的 过滤器 上,所以先查看 过滤器 的作用很有帮助。以下图片显示了单个请求的处理层级。

客户端给应用程序发送请求,容器创建一个包含过滤器ServletFilterChain ,它将处理基于请求 URI 路径的 HttpServletRequest。在 Spring MVC 应用程序中,ServletDispatcherServlet 的实例。一个 Servlet 最多只能处理一个 HttpServletRequestHttpServletReponse。不过,可以使用多个 过滤器 处理以下内容:

  • 阻止下游 过滤器 或者 Servlet 被调用。在这个实例中,过滤器 将写 HttpServletReponse
  • 修改下游 过滤器ServletHttpServletRequestHttpServletReponse

FilterChain 的能力来自于传入其中的 过滤器

FilterChain 用例

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// 在应用程序处理前,做一些逻辑处理
    chain.doFilter(request, response); // 调起应用程序处理
    // 在应用程序处理后,做一些逻辑处理
}

因为 过滤器 只影响下游的 过滤器Servlet,所以每个 过滤器 的调用顺序及其重要。

DelegatingFilterProxy

Spring 提供一个叫做 DelegatingFilterProxy 的实现,它桥接了 Servlet 容器的生命周期和 Spring 的 ApplicationContext。Servlet 容器允许其使用自己的标准注册 过滤器,但是它感应不到 Spring 定义的 bean。DelegatingFilterProxy 可以凭借 Servlet 容器的机制完成注册,但是所有的工作都委托给实现了 过滤器 的 Spring Bean。

以下是 DelegatingFilterProxy 如何融合 过滤器FilterChain 的图。

DelegatingFilterProxyApplicationContext 中查找并调用 Filter0 Bean。DelegatingFilterProxy 的伪代码如下。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// 加载注册为 Spring Bean 的过滤器
	// 例如在 DelegatingFilterProxy 中,delegate 是 Filter0 Bean 的实例
	Filter delegate = getFilterBean(someBeanName);
	// 将工作委托给 Spring Bean
	delegate.doFilter(request, response);
}

DelegatingFilterProxy 的另一个好处是可以延迟查找 过滤器 的实例。这种方式十分重要,原因是容器需要在启动之前注册 过滤器。不过,Spring 一般通过 ContextLoaderListener 加载 Spring 的 Bean,直到所有的 过滤器 注册完才结束。

FilterChainProxy

对 Spring Security 的 Servlet 支持,包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一种特殊 过滤器,它可以通过 SecurityFilterChain 委托很多 过滤器。因为 FilterChainProxy 是一个 Bean,所以它一般封装在 DelegatingFilterProxy 中。

SecurityFilterChain

SecurityFilterChainFilterChainProxy 用于决定在该请求种哪个 Spring Security 的过滤器应该被调用。

Security Filters 在 SecurityFilterChain 一般以 Bean 的方式存在,它们通过 FilterChainProxy 注册,从而替代 DelegatingFilterProxyFilterChainProxy 直接通过 Servlet 容器或 DelegatingFilterProxy 注册,提供了诸多好处。首先,它为 Spring Security 的 Servlet 提供支撑起点。因此,如果你要解决 Spring Security Servlet 的问题,在 FilterChainProxy 中添加 debug 断点是不错的开始。

第二,由于 FilterChainProxy 是使用 Spring Security 的重点,它可以执行一些必要的任务。例如,清除 SecurityContext,从而避免内存泄漏。还可以应用于 Spring Security 的 HttpFirewall,从而保护应用程序免受某些类型的攻击。

此外,在决定何时调用 SecurityFilterChain 时,它提供了更多的灵活性。在 Servlet 容器中,只基于 URL 调用 过滤器。不过,FilterChainProxy 通过利用 RequestMatcher 接口,可以根据 HttpServletRequest 中的任何事情决定调用。

事实上,FilterChainProxy 可以用于决定应该调用哪个 SecurityFilterChain 。因此可以为应用程序的不同切片提供一个总体的分隔配置。

在上图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。第一个匹配的 SecurityFilterChain 会被调用。如果发起一个 URL 为 /api/message 的请求,它将匹配 SecurityFilterChain 0 的模式 /api/** ,所以只会调用 SecurityFilterChain 0 ,即使也匹配 SecurityFilterChain n 。如果发起一个 URL 为 /messages 的请求,它不匹配 SecurityFilterChain 0 的模式 /api/**,所以 FilterChainProxy 将继续尝试每一个 SecurityFilterChain。如果没有匹配到,SecurityFilterChain 匹配的 SecurityFilterChain n 实例将会被调用。

注意,SecurityFilterChain 0 只配置了 3 个安全 过滤器。但是,SecurityFilterChain n 配置了 4 个 过滤器 实例。每个 SecurityFilterChain 可以孤立的配置,留意这一点很重要。事实上,SecurityFilterChain 可能不配置 过滤器,如果 Spring Security 要忽略某些请求。

Security Filters

Security Filters 通过 SecurityFilterChain API 插入到 [[#FilterChainProxy]] 中。[[#过滤器]] 的顺序十分重要。通常,无需了解 Spring Security 过滤器 的顺序。但是,有些时候了解它们排序是有益的。

以下是 Spring Security 过滤器的综合排序:

  • ChannelProcessingFilter
  • SecurityContextPersistenceFilter
  • WebAsyncManagerIntegrationFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

处理 Security 异常

ExceptionTranslationFilter 可以将 AccessDeniedExceptionAuthenticationException 转入 HTTP 响应中。

ExceptionTranslationFilter 作为 Security Filters 中一员插入到 [[#FilterChainProxy]] 中。

  1. 首先,ExceptionTranslationFilter 调用 Filter.doFilter(requeset,response) 以执行其余应用程序。
  2. 如果用户未认证或者是 AuthenticationException ,那么开始认证。
    1. 清除 SecurityContextHolder
    2. HttpServletRequest 保存在 RequestCache 中。当用户成功认证后,RequestCache 用于重现原始请求。
    3. AuthenticationEntryPoint 用于来自客户端的请求凭证。例如,重定向到登录页面或者发送 WWW-Authenticate 头文件。
  3. 另外,如果是 AccessDenieException,那么将拒绝访问。将唤起 AccessDeniedHandler 处理拒绝访问。
title: note
如果应用程序没有抛出 `AccessDeniedException` 或者 `AuthenticationException`,那么 `ExceptionTranslationFilter` 什么都不做。

ExceptionTranslationFilter 的伪代码如下:

try {
	filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication();
	} else {
		accessDenied();
	}
}
posted @ 2022-05-01 15:02  Zhang_Xiang  阅读(358)  评论(0编辑  收藏  举报