用户数据获取
# 用户数据获取
对于传统的web项目,用户信息通常是存储在HttpSession中,需要的时候直接从HttpServletRequest中通过getSession()
之类的方法进行获取。SpringSecurity框架中对于用户信息的存储其实也在HttpSession。不过为了方便使用,以及框架的设计,它被进行了一些封装。
# 用户信息的存储
Spring Security框架在用户成功登录后,会将用户信息保存在这2个关键的地方:
- SecurityContextHolder:这是Spring Security的核心部分,它负责存储当前线程上下文中的安全信息。用户登录成功后,其认证信息会被封装成
Authentication
对象,并放置在SecurityContextHolder
中。SecurityContextHolder
使用ThreadLocal
来实现,确保每个线程都有自己的安全上下文,不会与其他线程共享。 - HttpSession:虽然Spring Security内部使用
SecurityContextHolder
来管理用户信息,但实际上这些信息最终还是会被委托存储到HttpSession中(或者根据配置,可能是其他的会话存储机制)。这是因为SecurityContextPersistenceFilter
过滤器会在每次请求开始时从会话中恢复SecurityContext
,并在请求结束时将更新后的SecurityContext
保存回会话。这样做是为了跨请求维护用户认证状态。
综上所述,用户信息本质上仍是存储在会话中的(Session)。当然,强大的Security也支持用户信息通过其他机制(如JWT令牌、Token)来维护。但本章,重点在存储于会话中的部分。
# SecurityContextHolder
SecurityContextHolder
本身是一个用于存储当前线程安全上下文信息的工具类,同时它也是管理和访问当前线程安全上下文(SecurityContext
)的一个关键组件。安全上下文(SecurityContext
)则用于存储与当前用户认证相关的信息,比如用户的权限、角色、认证状态等。
它控制着用户信息存储位置,但是它并不直接决定用户信息存储的位置,如内存或HttpSession。实际上,SecurityContextHolder
利用一个策略接口SecurityContextHolderStrategy
来决定如何存储和检索SecurityContext
(包含认证信息的对象)。
# SecurityContext组件
SecurityContext
主要负责存储和表示与当前执行线程相关的安全信息,特别是关于用户认证和授权的状态。具体来说,SecurityContext
的核心功能和特点包括:
- 存储认证信息:最核心的功能是存储一个
Authentication
对象,这个对象代表了用户的认证状态,包括但不限于用户名、密码(通常是经过处理的,不会明文存储)、授予的权限、角色等。Authentication
是对用户身份验证结果的封装,表明了用户是谁以及他们被允许做什么。 - 授权决策基础:基于存储的
Authentication
信息,SecurityContext
为授权决策提供了基础。应用程序可以根据Authentication
中的角色或权限来判断用户是否有权访问某个资源或执行特定操作。 - 线程关联:虽然
SecurityContext
本身不直接管理线程关联,但它通过与SecurityContextHolder
的配合,实现了与执行线程的绑定。这意味着每个执行线程都有其自己的SecurityContext
实例,确保了多线程环境下安全信息的隔离。 - 可传递性:在某些情况下,如远程调用或异步处理时,可能需要跨线程传递安全上下文。
SecurityContext
可以通过序列化、复制等方式,确保在不同线程间也能正确地识别和应用用户的安全属性。 - 默认实现与定制:Spring Security提供了
SecurityContextImpl
作为默认的实现类,但同时也允许开发者自定义实现,以适应特定的安全策略或集成需求。
总之,SecurityContext
是Spring Security中用户认证和授权逻辑的核心承载体,它确保了安全信息在整个应用执行过程中的一致性和可用性,是实现安全控制的基础构件。
# Authentication对象
Authentication
对象是一个核心接口,用于代表用户的认证信息,它封装了关于主体(principal,即用户)的身份以及认证状态的详细数据。Authentication
对象在认证流程中扮演着关键角色,具体作用包括:
- 认证请求封装:在认证过程开始时,
Authentication
对象被用作封装用户提供的认证凭据,比如用户名和密码。这时,它代表的是一个待验证的认证请求。 - 认证结果持有:一旦用户成功通过认证,
Authentication
对象就会被填充更多的信息,包括但不限于用户权限、角色等,并且它会变成一个代表已认证主体的对象。此时,它包含了用户的所有认证状态信息,成为安全决策的重要依据。 - 权限检查:
Authentication
对象包含的principal
属性(通常是UserDetails
的实例)提供了用户的角色和权限信息,这对于执行访问控制至关重要。应用可以基于这些信息来决定用户是否被允许访问特定资源或执行特定操作。 - 线程上下文存储:认证后的
Authentication
对象通常会被存储在SecurityContext
中,进而由SecurityContextHolder
维护在当前线程的上下文中。这意味着在应用的任何地方,都可以方便地获取到当前用户的认证信息,而无需显式地传递认证对象。
在框架中,它————Authentication主要做两件事:用于认证,获取身份信息
作为AuthenticationManager的输入参数,提供用户身份认证的凭证,当它作为一个输入参数时,它的isAuthenticated方法返回false,表示用户还未认证。
代表已经经过身份认证的用户,此时的Authentication可以从SecurityContext中获取。
Java 中本身提供了Principal 接口用来描述认证主体,Principal可以代表一个公司、个人或者登录ID。Spring Security 中定义了 Authentication 接口用来规范登录用户信息, Authentication 继承自 Principal:
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
// 获取用户权限
Object getCredentials();
// 获取用凭证
Object getDetails();
// 获取用户的详细信息
Object getPrincipal();
// 获取当前用户的信息,可能是一个用户名 或者一个用户对象
boolean isAuthenticated();
// 验证当前用户是否认证成功
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
2
3
4
5
6
7
8
9
10
11
12
13
Principal
: 定义认证的用户,如果用户使用用户名/密码的方式登录,通常是一个包含用户详细信息的UserDetails
对象。Credentials
: 用户的凭证信息,通常指密码,但在认证成功后出于安全考虑通常会被清空,防止泄露。Authorities
: 代表用户具有的权限集合,通常以GrantedAuthority
的列表形式存在,用于授权决策。isAuthenticated()
: 判断认证是否已经完成,返回布尔值。
为什么要设计Authentication这个接口呢?
接口是一种类的规范或者约定,将身份信息设计成一个接口,这样,使用
Authentication
接口的代码就不依赖于具体的认证实现类,降低了模块之间的耦合度。当需要更换认证方式或实现细节时,只需提供一个新的实现类,而无需修改大量调用该接口的代码。通过该接口,实现了允许不同的认证机制(如数据库认证、LDAP认证、JWT认证等)共享相同的处理逻辑,提高了代码的复用性和灵活性。
# 不同的认证方式
不同的认证方式对应不同的Authentication实例,SpringSecurity中的Authentication实现类有很多:
- AbstractAuthenticationToken:该类实现了
Authentication
和CredentialsContainer
两个 接口,在AbstractAuthenticationToken中对Authentication接口定义的各个数据获取方法进行了实现, CredentialsContainer则提供了登录凭证擦除方法。一般在登录成功后,为了防止用户信息泄漏,可以将登录凭证(例如密码)擦除。 RememberMeAuthenticationToken
:如果用户使用RememberMe的方式登录,登录信息将封装在RememberMeAuthenticationToken中。- TestingAuthenticationToken:单元测试时封装的用户对象。
- AnonymousAuthenticationToken:匿名登录时封装的用户对象。
- RunAsUserToken:替换验证身份时封装的用户对象。
UsernamePasswordAuthenticationToken
:表单登录时封装的用户对象。- JaasAuthenticationToken: JAAS认证时封装的用户对象。
- PreAuthenticatedAuthenticationToken: Pre—Authentication场景下封装的用户对象。
在上述的实现中,最常用的就是2和6。
# 获取用户信息例子
在了解用户信息存储的大致情况后,接下来我们一起看看实际代码例子:
# 总结
总的来说,SecurityContextHolder
是一个线程局部的内存存储,而用户认证信息在请求间的一致性通常是通过将其委托存储到 HttpSession
中来实现的。
而SecurityContextHolder、SecurityContext、Authentication之间的关系为:SecurityContextHolder内部存储 SecurityContext。SecurityContext内部存储 Authentication