跳至主要內容

源码篇之认证组件

Cactus li...大约 6 分钟Spring SecuritySpring Security

1. 简介

本篇文章主要学习 Spring Security 中认证相关组件,为下篇分析用户名密码认证执行流程 打个基础。

2. 认证机制

Spring Security 提供了多种认证方式登录系统:

  • Username and Password :用用户名/密码进行认证
  • OAuth 2.0 Login: 使用 OpenID Connect OAuth 2.0登录
  • SAML 2.0 LoginSAML 2.0登录
  • Remember Me : 记住我
  • JAASJAAS认证
  • Pre-Authentication Scenarios : 使用外部机制进行认证
  • X509 : X509认证

3. 认证组件

Spring Security中认证相关组件有:

  • SecurityContextHolder : 上下文信息持有者,存储当前认证用户SecurityContext
  • SecurityContext :上下文信息,包含当前认证用户的 Authentication (认证信息),从 SecurityContextHolder中获取
  • Authentication : 认证信息
  • GrantedAuthority:授予的权限
  • AuthenticationManager :认证管理器, 被Spring Security Filter 调用执行认证
  • ProviderManager :认证提供者管理器,AuthenticationManager的实现
  • AuthenticationProvider :认证提供者,由 ProviderManager 用于执行特定类型的认证
  • AuthenticationEntryPoint :认证入口点,处理认证过程中的认证异常,比如:重定向到登录页面
  • AbstractAuthenticationProcessingFilter :一个用于认证的基础 Filter抽象类

3.1 SecurityContextHolder

SecurityContextHolder 是 Spring Security 中的一个核心类,用于存储和访问当前应用程序执行的安全上下文(SecurityContext)。SecurityContext 包含了当前应用程序执行的安全信息,例如认证主体(Principal)、授予的权限(Granted Authorities)等。

securitycontextholder
securitycontextholder

当用户认证成功后,会将 SecurityContext 设置到SecurityContextHolder中,后续流程可以通过 SecurityContextHolder 静态方法直接获取用户信息:

// 以下方式可以在环境中测试
SecurityContext context = SecurityContextHolder.getContext();// 获取 SecurityContext
Authentication authentication = context.getAuthentication();// 获取认证信息
String username = authentication.getName(); // 用户名
Object principal = authentication.getPrincipal(); // 当前用户的信息,通常是UserDetails的实例
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();// 权限

SecurityContextHolder 默认采用 ThreadLocal 来保存信息。在过滤器阶段,已认证的请求线程会从会话中提取认证信息,并将其存储在当前线程的 ThreadLocal 中。这使得业务代码能够方便地获取用户信息。一旦线程执行完毕,FilterChainProxy 会负责执行清理操作。

3.2 SecurityContext

SecurityContext 里主要包含了 Authentication认证对象,接口中只提供了两个简单方法:

/**
 * Obtains the currently authenticated principal, or an authentication request token.
 * @return the <code>Authentication</code> or <code>null</code> if no authentication
 * information is available
 */
 // 获取 Authentication
Authentication getAuthentication();

/**
 * Changes the currently authenticated principal, or removes the authentication
 * information.
 * @param authentication the new <code>Authentication</code> token, or
 * <code>null</code> if no further authentication information should be stored
 */
 // 设置 Authentication
void setAuthentication(Authentication authentication);

该接口的实现类 SecurityContextImpl 也很简单:

image-20240118171722859
image-20240118171722859

3.3 Authentication

Spring Security 中,Authentication 接口具有两个主要作用:

  • 作为预认证用户信息的容器,此时用户信息尚未完成认证。它在认证管理器中充当输入参数,用于提供认证凭证。

  • 表示当前已认证的用户,可以通过访问 SecurityContext 来获取当前的 Authentication 对象。这个对象包含了已通过认证的用户的信息。

Authentication 接口源码如下:

public interface Authentication extends Principal, Serializable {
	// 获取 授权信息 ,用户权限集合 => 可用于访问受保护资源时的权限验证
	Collection<? extends GrantedAuthority> getAuthorities();
	// 凭据 这通常是一个密码,被认证后被清除
	Object getCredentials();
    // 存储有关身份验证请求的其他详细信息。
	Object getDetails();
	// 被认证主体的身份。在带有用户名和密码的身份验证请求的情况下,这将是用户名
	Object getPrincipal();
	// 是否被认证
	boolean isAuthenticated();
	// 认证结果设置
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

}

Authentication 有很多实现类,对应了不同的认证方式,比如记住的身份验证时,使用的是 RememberMeAuthenticationToken

image-20240118174805422
image-20240118174805422

3.4 GrantedAuthority

GrantedAuthority用于表示已经获得的权限信息,可以是角色权限值

这个接口只定义了一个getAuthority方法,用于获取当前权限的标识符。默认的实现类是 SimpleGrantedAuthority

public interface GrantedAuthority extends Serializable {

	String getAuthority();

}

可以使用Authentication.getAuthorities()获取当前用户的权限集合:

 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();// 权限集合信息

3.5 AuthenticationManager

AuthenticationManager 是认证管理器,它被Security过滤器调用,用于执行认证操作。通常情况下,我们会使用ProviderManager作为常见的AuthenticationManager实现。

/**
 * Processes an {@link Authentication} request.
 * 认证管理器 实现认证主要是通过AuthenticationManager接口
 * 在实际开发中,我们可能有多种不同的认证方式,例如:用户名+
 * 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是
 * AuthenticationManager。
 *
 */
public interface AuthenticationManager {

	/**
	 * authenticate()方法主要做三件事:
	 *   如果验证通过,返回Authentication(通常带上authenticated=true)。
	 *   认证失败抛出 AuthenticationException
	 *   如果无法确定,则返回null
	 */
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

3.6 ProviderManager

ProviderManager(提供者管理器)类实现了 AuthenticationManager 接口。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	private static final Log logger = LogFactory.getLog(ProviderManager.class);

	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();

	private List<AuthenticationProvider> providers = Collections.emptyList();

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private AuthenticationManager parent;

	private boolean eraseCredentialsAfterAuthentication = true;

	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}


	public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}
}

ProviderManager 内部包含多个 AuthenticationProvider(认证提供者),在调用 ProviderManagerauthenticate 方法时,会循环遍历所有的 AuthenticationProvider。认证过程会一直持续,直到某个提供者返回认证成功,此时整个认证过程结束;如果所有提供者都无法完成认证,那么最终会返回认证失败。

providermanager
providermanager

3.7 AuthenticationProvider

每个AuthenticationProvider对应一种认证方式,执行实际的认证处理逻辑,比如用户名密码用的是DaoAuthenticationProvider

image-20240118180310737
image-20240118180310737

通过向 ProviderManager 注入多个 AuthenticationProvider,可以实现对认证方式的自定义扩展。例如,可以添加支持手机验证码登录的 AuthenticationProvider,从而实现多种认证方式的灵活组合。

3.8 AuthenticationEntryPoint

AuthenticationEntryPointExceptionTranslationFilter 检测到认证异常时被用于触发身份验证流程,比如重定向到登录页面。

几个常见的 AuthenticationEntryPoint 实现包括:

image-20240118180728322
image-20240118180728322

比如LoginUrlAuthenticationEntryPoint,会在表单登录失败时,执行重定向(或转发)到登录表单URL

3.9 AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 是基于浏览器的HTTP身份验证请求的抽象类,可以通过继承此类来开发认证过滤器。主要功能是提供对认证结果成功和失败的相关处理:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 1. 检查是否需要身份验证
    if (!requiresAuthentication(request, response)) {
        // 如果不需要身份验证,直接继续执行过滤器链
        chain.doFilter(request, response);
        return;
    }
    try {
        // 2. 尝试进行身份验证,获取身份验证结果
        Authentication authenticationResult = attemptAuthentication(request, response);
        // 3. 如果身份验证结果为null,表示子类指示身份验证尚未完成,立即返回
        if (authenticationResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            return;
        }
        // 4. 使用会话策略处理身份验证成功的情况
        this.sessionStrategy.onAuthentication(authenticationResult, request, response);
        // 5. 如果设置在身份验证成功之前继续执行过滤器链,则继续执行
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        // 6. 处理身份验证成功的情况
        successfulAuthentication(request, response, chain, authenticationResult);
    }
    catch (InternalAuthenticationServiceException failed) {
        // 7. 处理内部身份验证服务异常
        this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
        unsuccessfulAuthentication(request, response, failed);
    }
    catch (AuthenticationException ex) {
        // 8. 处理身份验证失败的情况
        unsuccessfulAuthentication(request, response, ex);
    }
}

你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3