写点什么

Spring Security

作者:Zhang
  • 2022 年 5 月 11 日
  • 本文字数:4581 字

    阅读完需:约 15 分钟

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 的 过滤器 上,所以先查看 过滤器 的作用很有帮助。以下图片显示了单个请求的处理层级。



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

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

  • 修改下游 过滤器 和 Servlet 的 HttpServletRequest 和 HttpServletReponse

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 的图。



DelegatingFilterProxy 从 ApplicationContext 中查找并调用 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

SecurityFilterChain 被 FilterChainProxy 用于决定在该请求种哪个 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 可以将 AccessDeniedException 和 AuthenticationException 转入 HTTP 响应中。

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



  1. 首先,ExceptionTranslationFilter 调用 Filter.doFilter(requeset,response) 以执行其余应用程序。

  2. 如果用户未认证或者是 AuthenticationException ,那么开始认证。清除 SecurityContextHolderHttpServletRequest 保存在 RequestCache 中。当用户成功认证后,RequestCache 用于重现原始请求。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();	}}
复制代码


发布于: 刚刚阅读数: 4
用户头像

Zhang

关注

还未添加个人签名 2020.12.27 加入

还未添加个人简介

评论

发布
暂无评论
Spring Security_Java_Zhang_InfoQ写作社区