1、SecurityContextHolder
SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。
获取用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!authentication.isAuthenticated()){
return null;
}
Object principal = authentication.getPrincipal();
String username = null;
if (principal instanceof org.springframework.security.core.userdetails.UserDetails){
username = ((UserDetails) principal).getUsername();
}else {
username = principal.toString();
}
return username;
getAuthentication()返回认证信息getPrincipal()返回身份信息。UserDetails是Spring对身份信息封装的一个接口
2、Authentication
/**
* Authentication在spring security中是最高级别的身份/认证的抽象
*/
public interface Authentication extends Principal, Serializable {
/**
* 权限信息列表,GrantedAuthority的实现类
* @return
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
* @return
*/
Object getCredentials();
/**
* 细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值
* @return
*/
Object getDetails();
/**
* 身份信息
* @return
*/
Object getPrincipal();
/**
* 是否认证
* @return
*/
boolean isAuthenticated();
/**
* 设置是否认证
* @param isAuthenticated
* @throws IllegalArgumentException
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Spring Security是如何完成身份认证的?
- 用户名和密码被过滤器获取到,封装成
Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。 AuthenticationManager身份管理器负责验证这个Authentication- 认证成功后,
AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。 SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
3、AuthenticationManager
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
AuthenticationManager接口是认证的核心接口,也是发起认证的出发点。在实现需求中,用户可能需要用户名/密码,手机/密码,或者开放平台unionId登录等等,所以AuthenticationManager并不直接认证。
ProviderManager是AuthenticationManager常用的实现类。ProviderManager维护着一个 List<AuthenticationProvider>列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。在默认策略下,只需要通过一个AuthenticationProvider的认证,即可被认为是登录成功。也就是说,核心的认证入口始终只有一个:AuthenticationManager。
ProviderManager代码
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
//AuthenticationProvider列表
private List<AuthenticationProvider> providers;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
//AuthenticationProvider列表的Iterator
Iterator var8 = this.getProviders().iterator();
//依次认证
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
//是否支持
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
//认证
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
}
if (result == null && this.parent != null) {
try {
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}
// 如果有Authentication信息,则直接返回
if (result != null) {
////移除密码
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
// //发布登录成功事件
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
//认证失败,包装异常信息
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
ProviderManager 中的List,会依照次序去认证,认证成功则立即返回,若认证失败则返回null,下一个AuthenticationProvider会继续尝试认证,如果所有认证器都无法认证成功,则ProviderManager 会抛出一个ProviderNotFoundException异常。
4、DaoAuthenticationProvider
DaoAuthenticationProvider最常用的一个实现类

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
//
private UserDetailsService userDetailsService;
//检索用户,
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//UserDetailsService 通过用户名去检索用户,实现中一般从数据库中
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
}
DaoAuthenticationProvider.retrieveUser()只是检索用户,真正认证的逻辑在父类AbstractUserDetailsAuthenticationProvider中
AbstractUserDetailsAuthenticationProvider中的认证代码
5、UserDetails与UserDetailsService
UserDetails接口,它代表了最详细的用户信息
public interface UserDetails extends Serializable {
/**
* 权限
* @return
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 密码
* @return
*/
String getPassword();
/**
* 用户名
* @return
*/
String getUsername();
/**
* 是否账号过期
* @return
*/
boolean isAccountNonExpired();
/**
* 是否锁定
* @return
*/
boolean isAccountNonLocked();
/**
* 用户凭证(密码)是否过期
* @return
*/
boolean isCredentialsNonExpired();
/**
* 启用禁用
* @return
*/
boolean isEnabled();
}
UserDetailsService只负责从特定的地方(通常是数据库)加载用户信息,UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载用户,后者从内存中加载用户,也可以自己实现UserDetailsService,通常这更加灵活。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
认证流程
常是数据库)加载用户信息,UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载用户,后者从内存中加载用户,也可以自己实现UserDetailsService,通常这更加灵活。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
认证流程

本文详细介绍了Spring Security的认证流程,包括SecurityContextHolder的作用、Authentication接口的功能、AuthenticationManager的工作原理、DaoAuthenticationProvider的具体实现以及UserDetails与UserDetailsService的职责。

1万+

被折叠的 条评论
为什么被折叠?



