一旦你熟悉了如何配置和启动 Spring Security 应用程序,你可能希望了解 Spring Security 框架背后的实际工作方式。本篇文章就来了解一下 Spring Security 框架的基本架构和身份验证的工作过程。


1. Spring Security 核心组件与架构

1.1 SecurityContextHolder、SecurityContext(安全上下文)

首先,最基本的对象就是SecurityContextHolder 。它存储了应用程序当前 SecuirtyContext(安全上下文)的详细信息(当前用户的信息,是否已经通过验证,拥有的权限,等等)。在默认的情况下,SecurityContextHolder 使用 ThreadLocal 策略来存储这些信息。所以即使没有将 SecuirtyContext 作为参数显式地传递给方法,也始终可以用于同一线程的方法中,在请求结束后 Spring Security 会自动为你清除线程。

1.2 Authentication

SecurityContext 存储了当前与应用程序交互的用户信息,Spring Security 使用一个 Authentication 对象来表示这些信息。

Authentication 源码:

package org.springframework.security.core;

public interface Authentication extends Principal, Serializable {
// 权限列表,通常是表示权限的字符串列表
Collection<? extends GrantedAuthority> getAuthorities();
// 密码信息,由用户输入的密码凭证。认证后会移出,来保证安全性
Object getCredentials();
// 细节信息,Web 应用中一般是访问者的 ip 地址和 sessionId
Object getDetails();
// 最重要的身份信息,一般返回 UserDetails 的实现类
Object getPrincipal();

boolean isAuthenticated();

void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

1.3 UserDetailsService、UserDetails

在上面的源码中,我们看到 Authentication.getPrincipal() 会返回一个 UserDetails 对象,该对象存储了最详细的用户信息,包含了一些必要的用户信息字段。

UserDetails 源码:

public interface UserDetails extends Serializable {
// 权限列表,通常是表示权限的字符串列表
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

UserDetailsService 接口负责加载用户信息,创建 UserDetails 对象。Spring Security 提供了几种常用的实现,但是通常推荐自己实现 UserDetailsService 接口更加灵活。

UserDetailsService 源码:

public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

1.4 GrantedAuthority

除了用户信息之外,Authentication 还提供了 getAuthorities() 方法。这个方法提供了 GrantedAuthority 对象数组。GrantedAuthority 是授予给用户的权限,这些权限通常是 “角色”。GrantedAuthority 对象通常由 UserDetailsService 加载。

1.5 总结

组件功能总结:

  • SecurityContextHolder 提供对 SecurityContext 对象的访问。
  • SecurityContext 用于保存 Authentication 和特定的安全请求信息。
  • Authentication 以 Spring Security 的特定方式表示主体。
  • UserDetails 提供用户的必要信息,以便构建 Authentication 对象。
  • UserDetailsService 用于创建 UserDetails 对象的接口。
  • GrantedAuthority 表示对应用程序访问的权限。

组件关系总结:

Spring Security组件关系图


2. Spring Security 身份验证

2.1 AuthenticationManager

在讲 Spring Security 身份验证过程前,我们先了解一下 AuthenticationManager 接口。它是身份验证的核心接口,这个接口只有一个方法,就是对用户信息的认证。Spring Security 同样为我们准备了默认的实现,但是根据业务的不同,还是推荐自己实现接口的认证方法。

AuthenticationManager 源码:

public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}

2.2 身份验证过程

我们先思考一般的身份验证会有哪些步骤呢?

  1. 提示用户输入用户名和密码。
  2. 系统验证用户名和密码是否正确(验证成功)。
  3. 获取用户的上下文信息(用户的角色列表,等等)。
  4. 创建用户的安全上下文。
  5. 用户继续进行其他操作,这些操作受到访问控制机制的保护,该机制根据当前上下文检查操作所需的权限。

以上列表的前四项构成了身份验证过程,我们来了解一下这些过程在 Spring Security 是如何发生的。

  1. 将用户的输入封装到 UsernamePasswordAuthenticationToken 实例中(Authentication 接口实例)。
  2. 将这个实例传递给 AuthenticationManager 实例进行验证。
  3. 验证成功后,AuthenticationManager 返回一个完全填充的 Authentication 实例。
  4. 安全上下文是通过调用 SecurityContextHolder.getContext().setAuthentication(…) 创建的,传入的是完全填充的 Authentication 实例。

这样我们就认为用户已经通过了身份验证。

2.3 身份验证流程图

Spring Security 身份验证流程图

本篇只对身份验证做了一个大概的了解,之后会继续深入探究身份验证的工作原理。


参考文献:Spring Security 5.2.3 官方文档

评论