1. SecurityContextHolder
SecurityContextHolder
默认使用ThreadLocal
策略来存储认证信息
ThreadLocal:与当前线程绑定,用来存储用户信息
获取当前用户信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
2. Authentication
Authentication包含用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息,通过SecurityContextHolder
类的getContext().getAuthentication()
返回一个认证信息。
Authentication 包含以下几个方法:
接口源码
package org.springframework.security.core;
public interface Authentication extends Principal, Serializable {
Collection extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
getAuthorities()
:权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials()
:密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails()
:细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的 ip 地址和 sessionId 的值。
getPrincipal()
:大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一。
3. UserDetails
身份信息封装的一个接口,可以通过 Authentication.getPrincipal()
获得相关的实现类。
4. AuthenticationManager
AuthenticationManager
认证相关的核心接口,身份管理器负责验证这个Authentication
,认证成功后, AuthenticationManager
身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication
实例。
代码示例
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
Authentication result = am.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed:" + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains:" +
SecurityContextHolder.getContext().getAuthentication());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List AUTHORITIES = new ArrayList();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
5. ProviderManager
ProviderManager
实现了AuthenticationManager
接口,内部会维护一个List
列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate),不同的认证方式使用不同的AuthenticationProvider
。
ProviderManager 源码
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// 维护一个 AuthenticationProvider 列表
private List providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
// 依次认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
catch (AuthenticationException e) {
lastException = e;
}
}
// 如果有 Authentication 信息,则直接返回
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// 移除密码
((CredentialsContainer) result).eraseCredentials();
}
// 发布登录成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
...
// 执行到此,说明没有认证成功,包装异常信息
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
}
6. DaoAuthenticationProvider
DaoAuthenticationProvider
实现了AuthenticationProvider
类,UserDetailsService
加载用户,提交的用户名和密码,被封装成了 UsernamePasswordAuthenticationToken
,与 retrieveUser
方法返回的UserDetails
类进行密码对比,比对密码的过程,用到了 PasswordEncoder
和 SaltSource
7. UML 结构图
Reference
https://www.cnkirito.moe/spring-security-1/