封面图片

编程

Spring Security入门:认证

引言

Spring Security是一个功能强大的身份验证和授权框架,用于保护基于Spring的应用程序的安全性。它提供了一系列功能,包括认证、授权、会话管理、密码管理、攻击防护等,使得开发者可以轻松地实现各种安全相关的功能。 以下是Spring Security的一些关键特性和组件:

  1. 认证(Authentication): Spring Security提供了多种身份验证方式,包括基于表单、基于HTTP基本认证、基于LDAP、OAuth等。它允许您通过用户名/密码、令牌、证书等方式验证用户身份。
  2. 授权(Authorization): Spring Security支持基于角色、权限的访问控制。您可以轻松定义哪些用户拥有访问应用程序中的特定资源的权限,以及如何根据这些权限进行访问控制。
  3. 会话管理(Session Management): Spring Security允许您对用户会话进行管理,包括设置会话超时、限制同时活动的会话数量、防止会话固定攻击等。
  4. 密码管理(Password Management): Spring Security提供了强大的密码编码器,用于加密和验证用户密码。它支持多种密码编码算法,包括BCrypt、SHA-256等,以及密码策略的配置。
  5. 攻击防护(Attack Protection): Spring Security帮助您防范各种安全攻击,包括CSRF(跨站请求伪造)、XSS(跨站脚本攻击)、SQL注入等。它提供了一系列的防护机制,以确保您的应用程序的安全性。
  6. OAuth和OpenID Connect支持: Spring Security提供了对OAuth2的全面支持,使得您可以轻松地集成第三方身份验证和授权服务到您的应用程序中。

那么Spring Security最重要的、也是最核心的功能就是认证Authentication)和授权Authorization)。

架构

SecurityFilterChain 被 FilterChainProxy 用来确定当前请求应该调用哪些 Spring Security Filter 实例。 FilterChainProxy 可以通过使用 RequestMatcher 接口,根据 HttpServletRequest 中的任何内容确定调用。 下图显示了多个 SecurityFilterChain 实例。

Security Filter

Security Filter 是通过 SecurityFilterChain API 插入 FilterChainProxy 中的。 这些 filter 可以用于许多不同的目的,如 认证、 授权、 漏洞保护 等等。filter 是按照特定的顺序执行的,以保证它们在正确的时间被调用,例如,执行认证的 Filter 应该在执行授权的 Filter 之前被调用。

添加自定义Filter到Filter Chain

大多数情况下,默认的 security filter 足以为你的应用程序提供安全。然而,有时你可能想在 security filter chain 中添加一个自定义的 filter。 例如,假设你想添加一个 Filter,获得一个租户 id header 并检查当前用户是否有访问该租户的权限。前面的描述已经给了我们一个添加 filter 的线索,因为我们需要知道当前的用户,所以我们需要在认证 filter 之后添加它。 首先,创建一个 Filter:(这里也可以用OncePerRequestFilter替代Filter)

1import org.springframework.security.access.AccessDeniedException; 2 3public class TenantFilter implements Filter { 4 5 @Override 6 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 7 HttpServletRequest request = (HttpServletRequest) servletRequest; 8 HttpServletResponse response = (HttpServletResponse) servletResponse; 9 10 String tenantId = request.getHeader("X-Tenant-Id"); 11 boolean hasAccess = isUserAllowed(tenantId); 12 if (hasAccess) { 13 filterChain.doFilter(request, response); 14 return; 15 } 16 throw new AccessDeniedException("Access denied"); 17 } 18 19}

把自定义的Filter添加到Filter Chain中:

1@Bean 2SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 3 http 4 // ... 5 .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); 6 return http.build(); 7}

通过在 AuthorizationFilter 之前添加filter,我们确保 TenantFilter 在认证 filter 之后被调用。

认证

认证涉及的重要的组件。

  • SecurityContextHolder - SecurityContextHolder 是 Spring Security 存储 认证 用户细节的地方。
  • SecurityContext - 是从 SecurityContextHolder 获得的,包含了当前认证用户的 Authentication (认证)。
  • Authentication - 可以是 AuthenticationManager 的输入,以提供用户提供的认证凭证或来自 SecurityContext 的当前用户。
  • GrantedAuthority - 在 Authentication (认证)中授予委托人的一种权限(即role、scope等)。
  • AuthenticationManager - 定义 Spring Security 的 Filter 如何执行 认证 的API。
  • ProviderManager - 最常见的 AuthenticationManager 的实现。
  • AuthenticationProvider - 由 ProviderManager 用于执行特定类型的认证。
  • 用AuthenticationEntryPoint请求凭证 - 用于从客户端请求凭证(即重定向到登录页面,发送 WWW-Authenticate 响应,等等)。
  • AbstractAuthenticationProcessingFilter - 一个用于认证的基本 Filter。这也让我们很好地了解了认证的高层流程以及各部分是如何协作的。

SecurityContextHolder

Spring Security 的认证模型的核心是 SecurityContextHolder。它包含了SecurityContext。 SecurityContextHolder 是 Spring Security 存储用户 验证 细节的地方。Spring Security 并不关心 SecurityContextHolder 是如何被填充的。如果它包含一个值,它就被用作当前认证的用户。

最简单的方法是直接设置 SecurityContextHolder 来表明用户已被认证。 例子:

1SecurityContext context = SecurityContextHolder.createEmptyContext(); 2Authentication authentication = 3 new TestingAuthenticationToken("username", "password", "ROLE_USER"); 4context.setAuthentication(authentication); 5 6SecurityContextHolder.setContext(context);
  1. 创建一个空的 SecurityContext
  2. 创建一个新的 Authentication 对象。Spring Security 并不关心在 SecurityContext 上设置了什么类型的 Authentication 实现。这里,我们使用 TestingAuthenticationToken,因为它非常简单。一个更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities)。
  3. SecurityContextHolder 上设置 SecurityContext。Spring Security 使用这些信息进行 授权。

我们如果需要拿到认证用户的信息,可以直接访问 SecurityContextHolder

1SecurityContext context = SecurityContextHolder.getContext(); 2Authentication authentication = context.getAuthentication(); 3String username = authentication.getName(); 4Object principal = authentication.getPrincipal(); 5Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些细节,这意味着 SecurityContext 对同一线程中的方法总是可用的,即使 SecurityContext 没有被明确地作为参数传递给这些方法。如果你注意在处理完当前委托人的请求后清除该线程,以这种方式使用 ThreadLocal 是相当安全的。Spring Security 的 FilterChainProxy 确保 SecurityContext 总是被清空。

SecurityContext

SecurityContext 是从 SecurityContextHolder 中获得的。SecurityContext 包含一个 Authentication 对象。

Authentication

Authentication 接口在Spring Security中主要有两个作用。

  • 对 AuthenticationManager 的一个输入,用于提供用户为验证而提供的凭证。当在这种情况下使用时,isAuthenticated() 返回 false。
  • 代表当前认证的用户。你可以从 SecurityContext 中获得当前的 Authentication。

认证(Authentication)包含了:

  • principal: 识别用户。当用用户名/密码进行认证时,这通常是 UserDetails 的一个实例。
  • credentials: 通常是一个密码。在许多情况下,这在用户被认证后被清除,以确保它不会被泄露。
  • authorities: GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色(role)和作用域(scope)。

GrantedAuthority

GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色(role)和作用域(scope)。 你可以从Authentication.getAuthorities()法中获得 GrantedAuthority 实例。这个方法提供了一个 GrantedAuthority 对象的集合。GrantedAuthority 是授予委托人的一种权限。这种授权通常是 “roles”,例如 ROLE_ADMIN。 当使用基于用户名/密码的认证时, GrantedAuthority 实例通常由 UserDetailsService 加载。

AuthenticationManager

AuthenticationManager 是定义 Spring Security 的 Filter 如何执行认证的API。返回的认证结果是由调用 AuthenticationManager的控制器(即 Spring Security的Filter实例)在 SecurityContextHolder 上设置的。 AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager

ProviderManager

ProviderManager是最常用的AuthenticationManager的实现。ProviderManager 委托给一个 List AuthenticationProvider实例。每个 AuthenticationProvider 都有机会表明认证应该是成功的、失败的,或者表明它不能做出决定并允许下游的 AuthenticationProvider 来决定。如果配置的 AuthenticationProvider 实例中没有一个能进行认证,那么认证就会以 ProviderNotFoundException 而失败,这是一个特殊的 AuthenticationException,表明 ProviderManager 没有被配置为支持被传入它的 Authentication 类型。

在实践中,每个 AuthenticationProvider 都知道如何执行特定类型的认证。例如,一个 AuthenticationProvider 可能能够验证一个用户名/密码,而另一个可能能够验证一个 SAML 断言。这让每个 AuthenticationProvider 在支持多种类型的认证的同时,可以做一种非常具体的认证类型,并且只暴露一个 AuthenticationManager Bean。

AuthenticationProvider

你可以在 ProviderManager 中注入多个 AuthenticationProvider 实例。每个 AuthenticationProvider 都执行一种特定类型的认证。例如, DaoAuthenticationProvider 支持基于用户名/密码的认证,而 JwtAuthenticationProvider 支持认证JWT令牌。

AuthenticationEntryPoint

AuthenticationEntryPoint用于发送一个要求客户端提供凭证的HTTP响应。 客户端向他们未被授权访问的资源发出未经认证的请求。在这种情况下, AuthenticationEntryPoint 的实现被用来请求客户端的凭证。 AuthenticationEntryPoint的实现可能会执行重定向到一个登录页面,或返回一个带401未认证的状态信息给客户端。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 被用作验证用户凭证的基础 Filter。

  1. 当用户提交他们的凭证时,AbstractAuthenticationProcessingFilter 会从 HttpServletRequest 中创建一个要认证的Authentication。创建的认证的类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilterHttpServletRequest 中提交的 usernamepassword 创建一个 UsernamePasswordAuthenticationToken
  2. Authentication 被传入 AuthenticationManager,以进行认证。
  3. 如果认证失败,则为 Failure。SecurityContextHolder 被清空。AuthenticationFailureHandler 被调用。参见 AuthenticationFailureHandler 接口。
  4. 如果认证成功,则为 Success

Demo

这是一个github上的demo:Spring Security Demo

更详细的类与接口的介绍

要理解Spring Security如何进行认证和授权和我们如何基于Spring Security做认证和授权的开发,熟悉Spring Security中扮演重要角色的的类的作用和功能是前提。 这里列出了Spring Security中关键的一些类和接口:

AuthenticationManager接口

认证管理器负责处理用户的身份验证(认证)请求,验证用户提供的凭据是否有效,并根据验证结果来确定用户是否通过身份验证。在Spring Security中,认证管理器通常由AuthenticationManager接口的实现类来实现。 在Spring Security中,您可以通过配置AuthenticationManagerBuilder来配置认证管理器。您可以指定不同的认证策略,比如基于内存、数据库、自定义等不同的认证方式。

AuthenticationManagerBuilder类

AuthenticationManagerBuilder是Spring Security提供的一个用于构建AuthenticationManager的构建器。它允许您以一种声明性的方式配置Spring Security中的身份验证机制,包括指定用户存储、密码编码器、认证提供者等。 以下是AuthenticationManagerBuilder的一些关键特点和用法:

  1. 配置用户存储: 使用AuthenticationManagerBuilder可以配置用户存储的方式,比如内存、数据库、LDAP等。您可以使用.inMemoryAuthentication()方法配置内存中的用户存储,也可以使用.userDetailsService(userDetailsService)方法配置自定义的用户详情服务。
1auth.inMemoryAuthentication() 2.withUser("user").password("{noop}password").roles("USER");
  1. 配置密码编码器: 为了安全地存储用户密码,Spring Security推荐使用密码编码器对密码进行加密。您可以使用.passwordEncoder(passwordEncoder)方法配置密码编码器。
1auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  1. 配置认证提供者:AuthenticationManagerBuilder允许您添加自定义的认证提供者,以满足特定的身份验证需求。您可以使用.authenticationProvider(authenticationProvider)方法添加自定义的认证提供者。
1auth.authenticationProvider(customAuthenticationProvider);

以上所有配置项都是通过重写WebSecurityConfigurerAdapter的configure方法。

1@Override 2protected void configure(AuthenticationManagerBuilder auth) throws Exception { 3 auth.inMemoryAuthentication() 4 .withUser("user").password("password").roles("USER"); 5} 6

ProviderManager类

ProviderManager是Spring Security中用于管理一组AuthenticationProvider的实现类。它是AuthenticationManager接口的默认实现之一。ProviderManager负责协调和委派身份验证请求给配置的一组AuthenticationProvider,直到成功验证用户身份或者所有提供者都无法验证身份。 ProviderManager是Spring Security中用于管理一组身份验证提供者的核心组件。 以下是ProviderManager的一些关键特点和功能:

  1. 委派给多个身份验证提供者:ProviderManager可以配置多个AuthenticationProvider,每个提供者负责一种身份验证机制,比如基于用户名密码、基于令牌、基于LDAP等。当收到身份验证请求时,ProviderManager会依次委派给每个提供者进行身份验证,直到找到一个能够成功验证的提供者为止。
  2. 支持多种身份验证方式:ProviderManager支持多种身份验证方式,包括用户名密码认证、令牌认证、证书认证等。开发者可以根据应用程序的需求配置不同类型的身份验证提供者。
  3. 异常处理: 如果所有配置的身份验证提供者都无法验证用户身份,ProviderManager将抛出ProviderNotFoundException异常。开发者可以通过配置ProviderManagerparentAuthenticationManager属性来指定备用的身份验证管理器。
  4. 自动配置: 在Spring Security的自动配置中,ProviderManager通常会与DaoAuthenticationProvider一起使用,后者用于基于用户名密码的身份验证。但是,您也可以根据自己的需要配置自定义的身份验证提供者。

AuthenticationProvider接口

AuthenticationProvider接口定义了身份验证的核心方法:

  • authenticate(Authentication authentication): 这是身份验证的入口点。当Spring Security需要验证用户身份时,它将传递一个Authentication对象给authenticate()方法,该对象包含用户提供的凭据信息。AuthenticationProvider实现类应该根据凭据信息验证用户身份,并返回一个Authentication对象表示已验证的用户身份,或者抛出AuthenticationException表示身份验证失败。
  • supports(Class<?> authentication): 这个方法用于指定该AuthenticationProvider支持的Authentication对象类型。通常情况下,supports()方法会检查传入的Authentication对象是否与该提供者兼容,如果是,则返回true,否则返回false

DaoAuthenticationProvider类

DaoAuthenticationProviderAuthenticationProvider接口的一个实现,它使用数据访问对象(DAO)来获取用户的身份信息。具体来说,它使用一个UserDetailsService来加载用户的详细信息,然后对用户提供的凭据进行验证。DaoAuthenticationProvider是Spring Security中常用的AuthenticationProvider实现之一。 DaoAuthenticationProvider的主要特点包括:

  • 使用UserDetailsService来加载用户信息:DaoAuthenticationProvider依赖于一个UserDetailsService,它负责加载用户的详细信息,包括用户名、密码和权限等。
  • 使用密码编码器进行密码验证:DaoAuthenticationProvider使用密码编码器来验证用户提供的密码是否与数据库中存储的密码匹配。这可以防止密码泄露和安全问题。
  • 自定义身份验证逻辑:DaoAuthenticationProvider允许开发者通过重写additionalAuthenticationChecks()方法来自定义身份验证逻辑。例如,您可以在此方法中实现对用户账号状态的检查,或者对密码过期等进行验证。

Authentication接口

在 Spring Security 中,Authentication(认证)是代表用户的身份验证信息的接口。它包含了有关用户身份验证的各种信息,例如用户名、密码、权限等。当用户成功登录系统时,Spring Security 将创建一个 Authentication 对象来表示用户的身份,并将其存储在安全上下文中,以便在用户访问受保护的资源时进行身份验证和授权。 Authentication 接口的常见实现类是 UsernamePasswordAuthenticationToken,用于表示基于用户名和密码的认证信息。除了用户名和密码之外,Authentication 对象还可以包含其他信息,例如用户的权限列表、认证方式等。 Spring Security 提供了许多功能来处理身份验证和授权,包括用户身份验证、密码加密、会话管理、访问控制等。通过配置 Spring Security,可以轻松地集成认证和授权机制到 Spring 应用程序中,确保系统的安全性和可靠性。 在 Spring Security 中,认证是用户登录系统的过程,而授权则是系统根据用户的身份和权限,决定用户是否被允许访问某些资源或执行某些操作。Authentication 对象在整个身份验证和授权过程中起着重要作用,它是 Spring Security 中表示用户身份的核心接口。

UsernamePasswordAuthenticationToken类

Authentication 接口的常见实现类。可以参考该类实现自己的用户认证类。

WebSecurityConfigurerAdapter抽象类

WebSecurityConfigurerAdapter是Spring Security提供的一个方便的基类,用于帮助配置Spring Security。通过继承WebSecurityConfigurerAdapter并重写其中的方法,开发者可以轻松地定制Spring Security的配置,包括安全规则、身份验证方式、授权规则等。 请注意:Spring Security 6.x中已将该类标注为过时的类,请使用如下方式替换:

1@Configuration 2@EnableWebSecurity 3public class SecurityConfig { 4 5 @Bean 6 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 7 http 8 .authorizeRequests() 9 .antMatchers("/public/**").permitAll() 10 .antMatchers("/admin/**").hasRole("ADMIN") 11 .antMatchers("/user/**").hasRole("USER") 12 .anyRequest().authenticated() 13 .and() 14 .formLogin() 15 .loginPage("/login") 16 .permitAll() 17 .and() 18 .logout() 19 .logoutUrl("/logout") 20 .permitAll(); 21 return http.build(); 22 } 23}

在Spring Security 5.x中可以使用,介绍如下: WebSecurityConfigurerAdapter提供了一系列的方法,可以用来配置Spring Security的各种行为。以下是其中一些常用的方法:

  1. configure(HttpSecurity http): 该方法用于配置HTTP安全规则。通过重写这个方法,您可以定义哪些URL路径需要进行安全控制,以及如何进行安全控制(如基于角色的访问控制、CSRF防护、跨域资源共享等)。
javaCopy code @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .antMatchers("/public/**").permitAll() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .logoutUrl("/logout") .permitAll(); }
  1. configure(AuthenticationManagerBuilder auth): 该方法用于配置身份验证管理器。通过重写这个方法,您可以配置身份验证机制,包括用户存储方式、密码编码器、自定义认证提供者等。
javaCopy code @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("{noop}password").roles("USER") .and() .withUser("admin").password("{noop}password").roles("ADMIN"); }
  1. configure(WebSecurity web): 该方法用于配置Spring Security的Filter链。通过重写这个方法,您可以配置哪些请求应该被忽略、跳过Spring Security的Filter链的处理。
javaCopy code @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**"); }
  1. configureGlobal(AuthenticationManagerBuilder auth): 该方法用于配置全局的身份验证管理器。它是configure(AuthenticationManagerBuilder auth)方法的一个替代品,用于全局配置。
javaCopy code @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("{noop}password").roles("USER") .and() .withUser("admin").password("{noop}password").roles("ADMIN"); }

通过继承WebSecurityConfigurerAdapter并重写这些方法,开发者可以方便地定制和配置Spring Security,以满足不同应用程序的安全需求。

2024年04月25日
在初学者眼中,世界充满了可能;专家眼中,世界大都已经既定。--铃木俊隆