Spring Security 6.x 一文快速搞懂配置原理

简介: 本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。

spring_security_lg-1280x720.png

一、基本概念

Spring Security框架看似比较复杂,但说到底,框架中的各种安全功能,基本上也就是一个个Filter(javax.servlet.Filter)组成的所谓“过滤器链”实现的,这些Filter以职责链的设计模式组织起来,环环相扣,不过在刚接触Spring Security框架时不必盯着每个Filter着重去研究,我们首要的目的是学会如何对Spring Security进行配置,很多人,特别是新手,在看过官方文档中配置示例代码(如下所示)之后,在没有足够背景知识的情况下,都会对这个http.build()方法感到莫名的困惑,想要定制开发也不知从何下手,本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。

版本说明:下文所贴出的各段源码均源自6.2.3版本,但其实5.7以上的各个版本,跟配置相关的代码基本相同,变动不算太大

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

概况地说,HttpSecurity的配置过程,主要就是向这个SecurityFilterChian中添加不同功能的Filter对象,为了方便后文理解,首先来看一下其中涉及的几个重要的接口和类(关系如下图)

image.png

接口

  • SecurityBuilder:顶层接口,定义了抽象的泛型构造器方法——build()
  • SecurityConfigurer:顶层接口,用来定义配置类的通用方法,每个Filter都是由特定的SecurityConfigurer的实现类构建出来并添加到FilterChain中的
  • SecurityFilterChain:顶层接口,即过滤器链,定义了获取List的方法,以及matches,用于判断某个请求是否满足进链的条件
  • HttpSecurityBuilder:继承SecurityBuilder,定义了构建SecurityFilterChain过程中的各种辅助方法,如添加Filter到SecurityFilterChain,获取SecurityConfigurer配置实现类等

  • AbstractSecurityBuilder:顶层的抽象父类,它没有实现build具体的逻辑,实际交由doBuild方法实现,只是用CAS对doBuild过程进行了并发控制
  • AbstractConfiguredSecurityBuilder:继承了AbstractSecurityBuilder,它内部维护了一个SecurityConfigurer的列表,实现了doBuild方法,确立了整个构建的流程
  • HttpSecurity 作为final实现类,它主要面向开发者,我们在开发过程中就是用它提供的一系列的配置入口,方便开发者对SecurityFilterChain中不同的Filter进行定制,包括添加自定义的Filter,关闭某些Filter,或扩展原来Filter的能力等等


二、基本流程

接下来,重点分析一下AbstractConfiguredSecurityBuilder类,整个构建过程围绕doBuild方法,主要分为:

  1. 初始化,包括beforeInit和init方法,其中beforeInit是扩展用的钩子方法,默认实现为空
  2. 配置,包括beforeConfigure和configure方法,其中beforeConfigure是扩展用的钩子方法,默认实现为空
  3. 构造,即performBuild方法,具体由HttpSecurity实现,主要是对Filter进行排序,并最终返回DefaultSecurityFilterChain的实例

而在AbstractConfiguredSecurityBuilder中维护了一个Map>对象,用于缓存各种SecurityConfigure的实现类,当调用init和configure时,实际上就会遍历这个Map所有configurer,依次调用对应方法,通常就是在configure方法中,将Filter加入到FilterChain中(下文详述)

protected final O doBuild() throws Exception {
    synchronized (this.configurers) {
       this.buildState = BuildState.INITIALIZING;
       beforeInit();
       init();
       this.buildState = BuildState.CONFIGURING;
       beforeConfigure();
       configure();
       this.buildState = BuildState.BUILDING;
       O result = performBuild();
       this.buildState = BuildState.BUILT;
       return result;
    }
}
 
private void configure() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {
       configurer.configure((B) this);
    }
}


那么,这些SecurityConfigure实例则是如何添加上述的Map中的?可以在HttpSecurityConfiguration找到相关的实现,源码如下

@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
    LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
    AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
          this.objectPostProcessor, passwordEncoder);
    authenticationBuilder.parentAuthenticationManager(authenticationManager());
    authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
    HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
    WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
    webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
    // @formatter:off
    http
       .csrf(withDefaults())
       .addFilter(webAsyncManagerIntegrationFilter)
       .exceptionHandling(withDefaults())
       .headers(withDefaults())
       .sessionManagement(withDefaults())
       .securityContext(withDefaults())
       .requestCache(withDefaults())
       .anonymous(withDefaults())
       .servletApi(withDefaults())
       .apply(new DefaultLoginPageConfigurer<>());
    http.logout(withDefaults());
    // @formatter:on
    applyCorsIfAvailable(http);
    applyDefaultConfigurers(http);
    return http;
}

可以看到在构造过程中(那段很长链式配置),已经帮我们添加了若干SecurityConfgurer实例,因此我们在使用配置SecurityFilterChain时,仅需要很少的配置就可以得到一个完整的具备基本功能的SecurityFilterChain,当然我们也可以利用这些配置项做很多定制开发。事实上,HttpSecurity大约提供了24个Filter相关的Configurer配置方法,其中11个Filter是默认加载的,整理成表格:

序号

方法

对应Filter

作用

1

headers

HeaderWriterFilter(默认加载)

用于添加Security HTTP header到response中,例如X-Frame-Options

2

cors

CorsFilter(通常在Spring MVC环境时默认加载)

用于支持跨域请求

3

sessionManagement

SessionManagementFilter

ConcurrentSessionFilter

DisableEncodeUrlFilter(默认生效)

ForceEagerSessionCreationFilter

用于管理session,如保存session,并发控制等操作

4

jee

J2eePreAuthenticatedProcessingFilter

用于支持Java EE 容器预认证

5

x509

X509AuthenticationFilter

用于支持X.509证书预认证,通常是指在浏览器中使用HTTPS协议

6

rememberMe

RememberMeAuthenticationFilter

用于在登录时记录用户登录信息,以保持登录态

7

authorizeHttpRequests

AuthorizationFilter

用于实现授权访问的逻辑

8

requestCache

RequestCacheAwareFilter(默认加载)

用于实现用户登录之后,跳转回登录之前请求的地址

9

exceptionHanding

ExceptionTranslationFilter(默认加载)

用于配置异常处理

10

securityContext

SecurityContextHolderFilter(默认加载)

SecurityContextPersistenceFilter(旧版本,现不推荐使用)

用于加载用户的登录态信息,并保存在SecurityContextHolder中

11

servletApi

SecurityContextHolderAwareRequestFilter(默认加载)

用于将HttpServletRequest包装为Servlet3SecurityContextHolderAwareRequestWrapper,方便其他Filter使用

12

csrf

CsrfFilter(默认加载)

用于防范跨站请求伪造(CSRF)攻击

13

logout

LogoutFilter(默认加载)

用于实现登出注销逻辑

14

anonymous

AnonymousAuthenticationFilter(默认加载)

用于实现匿名登录逻辑

15

formLogin

UsernamePasswordAuthenticationFilter

用于实现用户名密码登录逻辑

16

saml2Login

Saml2WebSsoAuthenticationFilter

用于实现SAML 2.0认证协议登录逻辑

17

saml2Logout

Saml2LogoutRequestFilter

Saml2LogoutResponseFilter

Saml2RelyingPartyInitiatedLogoutFilter

用于实现SAML 2.0认证协议登出逻辑

18

oauth2Login

OAuth2AuthorizationRequestRedirectFilter

用于接入OAuth2.0认证协议登录逻辑,例如Github登录

19

oidcLogout

OidcBackChannelLogoutFilter

用于实现OIDC认证协议登出逻辑

20

oauth2Client

OAuth2AuthorizationRequestRedirectFilter

OAuth2AuthorizationCodeGrantFilter

用于实现OAuth2.0客户端逻辑,例如授权码模式等

21

oauth2ResourceServer

BearerTokenAuthenticationFilter

用于实现OAuth2.0服务端逻辑

22

requiresChannel

ChannelProcessingFilter

用于判断哪些资源需要被保护

23

httpBasic

BasicAuthenticationFilter

用于实现HTTP基础认证的逻辑

24

passwordManagement

RequestMatcherRedirectFilter("/change-password")

用于实现在需要修改密码时,跳转页面的逻辑

25

HttpSecurityConfiguraion直接添加

WebAsyncManagerIntegrationFilter(默认加载)

用于在异步线程中,支持通过SecurityContextHolder获取认证对象Authentication

如果我们不对HttpSecurity做任何改动的话,默认得到的SecurityFilterChain是如下这样的,先了解大概,后续还针对部分重要的filter做深入分析。

org.springframework.security.web.session.DisableEncodeUrlFilter
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextHolderFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.web.filter.CorsFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.access.ExceptionTranslationFilter

三、SecurityConfigurer举例

这里还是以Spring Security官方文档中配置的示例代码为例,配置代码只需几行,比较优雅,这种设计是值得学习的,尽量让复杂的配置逻辑封装起来,让开发者在使用时,只需要关注业务逻辑即可

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

Spring Security提供了两种方式进行配置,一种就是示例代码中,即利用lambda表达式实现配置逻辑,这是5.5版本引入的,在这之前是使用无参的方法获取配置对象 ,然后进行链式的配置,如上述示例代码可以改写为

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.
            authorizeHttpRequests().anyRequest().authenticated()
            .and().formLogin()
            .and().httpBasic();
    return http.build();
}

以authorizeHttpRequests为例看一下源码实现,可以看到两种方式大同小异,只是使用了Customer函数式接口进行了封装

@Deprecated(since = "6.1", forRemoval = true)
public AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizeHttpRequests()
       throws Exception {
    ApplicationContext context = getContext();
    return getOrApply(new AuthorizeHttpRequestsConfigurer<>(context)).getRegistry();
}
public HttpSecurity authorizeHttpRequests(
       Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequestsCustomizer)
       throws Exception {
    ApplicationContext context = getContext();
    authorizeHttpRequestsCustomizer
       .customize(getOrApply(new AuthorizeHttpRequestsConfigurer<>(context)).getRegistry());
    return HttpSecurity.this;
}

其中getOrApply方法,用于获取到Configurer的具体实例(上文中提到过在AbstractConfiguredSecurityBuilder中维护了一个Configurers的Map,这些Configurer实例便是从这个Map获取的)

不过第二种写法,根据源码的注释,应该会在Spring Security 7版本里面移除,所以还是要适应这种Customizer参数的配置方法。

上面authorizeHttpRequests方法返回的是AuthorizeHttpRequestsConfigurer类中的AuthorizationManagerRequestMatcherRegistry对象,可以先看一下AuthorizeHttpRequestsConfigurer中的configure方法

public void configure(H http) {
    AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
    AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
    authorizationFilter.setAuthorizationEventPublisher(this.publisher);
    authorizationFilter.setShouldFilterAllDispatcherTypes(this.registry.shouldFilterAllDispatcherTypes);
    authorizationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
    http.addFilter(postProcess(authorizationFilter));
}

这里创建了一个AuthorizationFilter,并添加到HttpSecurity的List中,而AuthorizationManagerRequestMatcherRegistry则又是一个构造器模式实现的配置类,主要功能就是配置一些权限拦截的具体逻辑,如哪些地址需要什么角色访问等,这里就不展开了。

再看一下formLogin的例子

public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
    formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
    return HttpSecurity.this;
}

formLogin方法实际上创建FormLoginConfigurer的示例,该类主要用于创建UsernamePasswordAuthenticationFilter,即默认的用户名密码认证的过滤器

其中的configure方法由父类AbstractAuthenticationFilterConfigurer实现,源码如下,虽然这个方法有点长,但基本是围绕配置UsernamePasswordAuthenticationFilter实例而展开(this.authFilter就是UsernamePasswordAuthenticationFilter的实例,它在FormLoginConfigurer的构造函数中创建出来),主要就是创建用户认证所用到的一些基本组件,例如AuthenticationManager用于封装不同的用户认证方式(如用户名密码),AuthenticationSuccessHandler用于封装认证成功后执行的操作,AuthenticationFailureHandler用于封装认证失败后执行的操作等等

@Override
public void configure(B http) throws Exception {
    PortMapper portMapper = http.getSharedObject(PortMapper.class);
    if (portMapper != null) {
       this.authenticationEntryPoint.setPortMapper(portMapper);
    }
    RequestCache requestCache = http.getSharedObject(RequestCache.class);
    if (requestCache != null) {
       this.defaultSuccessHandler.setRequestCache(requestCache);
    }
    this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
    this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
    this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
    if (this.authenticationDetailsSource != null) {
       this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
    }
    SessionAuthenticationStrategy sessionAuthenticationStrategy = http
       .getSharedObject(SessionAuthenticationStrategy.class);
    if (sessionAuthenticationStrategy != null) {
       this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
    }
    RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
    if (rememberMeServices != null) {
       this.authFilter.setRememberMeServices(rememberMeServices);
    }
    SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
    if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
       SecurityContextRepository securityContextRepository = securityContextConfigurer
          .getSecurityContextRepository();
       this.authFilter.setSecurityContextRepository(securityContextRepository);
    }
    this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
    F filter = postProcess(this.authFilter);
    http.addFilter(filter);
}

通过上面两个源码示例,可以看到配置Filter的过程其实并不复杂,当我们在研究Spring Security不同过滤器功能时,可以参考源码中configure的配置过程,分析它们有哪些配置项,这些配置点主要能提供什么样能力的等,从而打开思路,快速实现不同的定制需求。

四、总结

最后做一个简单的总结,如图所示:

  1. 从HttpSercurityConfiguration定义HttpSecurity的Bean对象开始,便向HttpSecurity中添加了若干SecurityConfigurer对象,另外我们可以在自定义的配置类中对其进行一些定制调整
  2. 然后当调用HttpSecurity#Build()方法时,就会将取得所有SecurityConfigurer进行遍历,依次调用对应的init和configurer方法,而在configurer方法中,创建出各种功能的Filter实例,并添加到List列表中
  3. 最后通过performBuild方法,将List进行排序,并创建出DefaultFilterChian至此整个过滤器链的构建就完成了。

image.png

相关文章
|
13天前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
47 0
|
15天前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
15天前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
16天前
|
设计模式 监控 Java
解析Spring Cloud中的断路器模式原理
解析Spring Cloud中的断路器模式原理
|
16天前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
18小时前
|
安全 Java Spring
在Spring中,如何配置Bean的初始化方法和销毁方法
在Spring中,如何配置Bean的初始化方法和销毁方法
|
6天前
|
缓存 Java 数据库连接
Spring c3p0配置详解
【7月更文挑战第13天】
|
16天前
|
JSON 运维 监控
Spring Boot中的健康检查端点配置
Spring Boot中的健康检查端点配置
|
15天前
|
前端开发 Java 应用服务中间件
Spring Boot自动配置详解
Spring Boot自动配置详解
|
16天前
|
XML Java 数据格式
深入理解Spring中的依赖注入原理
深入理解Spring中的依赖注入原理