前面几篇直接讲了Demo,但是可能还是有点混乱,这里再将整个认证过程梳理一下,加深对前面的理解
一、标准的身份验证方案
对一个系统来说,标准的安全身份验证方案应该按如下步骤:
- 用户使用用户名和密码登录;
- 系统验证用户的额密码正确,成功登录;
- 获取该用户的上下文,即该用户的角色列表;
- 为用户建立安全的上下文
- 用户可能会继续执行某些操作,该操作可能会受到访问控制机制的保护,该访问控制机制会根据当前安全上下文信息检查该操作所需的权限
其中,前四个步骤构成了认证过程,在SpringSecurity中流程如下:
- 获取用户名和密码,并将其组合到一个Authentication实例,一般为UsernamePasswordAuthenticationToken实例中
- token会被传送到AuthenticationManager实例进行验证
- 成功验证后,AuthenticationManager会返回一个Authentication实例
- 通过调用SecurityContextHolder.getContext().setAuthentication(…)并传入返回的身份验证对象来建立安全上下文
来看一下涉及到的类
1.1 Authentication
继承了Principal类和序列化
public interface Authentication extends Principal, Serializable {
/**
* 获得用户的权限信息列表
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 密码信息,但认证后通常会被移除
*/
Object getCredentials();
/**
*
* 存储额外的认证请求信息,可能是IP地址等
*/
Object getDetails();
/**
* 犯规身份信息
*/
Object getPrincipal();
/**
* @return true if the token has been authenticated and the
*/
boolean isAuthenticated();
/**
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
从上面的源码中可以看出,由这个接口的实现类,我们可以得到用户相关的
权限列表信息,密码、用户其他详细信息等重要信息
1.2 AuthenticationManager
AuthenticationManager是一个接口,正常系统应用中,登录是可以通过多种密码验证方式,如手机号+验证码,用户名/邮箱+密码等。这个时候就需要调用SpringSecurity对它的默认实现ProviderManager,但是它本身并不处理身份验证请求,而是委托给已经配置的AuthenticationProvider列表,依次查看是否可以执行身份验证,每个AuthenticationProvider程序都会引发一场或返回完全填充的Authentication对象。如上面所说的几种验证方式,只要通过一个AuthenticationProvider并返回Authentication对象,即可通过登录验证
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
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(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();
}
private void checkState() {
if (parent == null && providers.isEmpty()) {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
}
}
/**
*
*/
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;
//遍历所有的AuthenticationProvider,因此进行认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e