AuthenticationManager
处理Authentication
请求,上一篇博客已经介绍了Authentication
,Spring Security
在进行身份验证时,会创建身份验证令牌,即Authentication
实例,提供给AuthenticationManager
接口的实现进行处理。
- Spring Security:身份验证令牌Authentication介绍与Debug分析
public interface AuthenticationManager {
/**
* 尝试对传递的Authentication对象进行身份验证
* 如果验证成功,则返回完全填充的Authentication对象(包括授予的权限)
* AuthenticationManager必须遵守以下关于异常的约定:
* 如果帐户被禁用并且AuthenticationManager可以测试此状态,则必须抛出DisabledException
* 如果帐户被锁定并且AuthenticationManager可以测试帐户锁定,则必须抛出LockedException
* 如果提供了不正确的凭证(比如密码),则必须抛出BadCredentialsException
* 参数:authentication - 身份验证请求对象
* 返回:一个完全经过身份验证的对象,包括凭证
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
AuthenticationManager
接口只有一个实用的实现类ProviderManager
,其他的实现类都是内部类,并且不提供身份验证处理的具体实现。
ProviderManager
通过AuthenticationProvider
列表迭代验证Authentication
请求。AuthenticationProvider
通常会按顺序尝试,直到提供非空响应,非空响应表示AuthenticationProvider
有权决定身份验证请求,并且不再尝试其他AuthenticationProvider
。 如果后续AuthenticationProvider
成功验证了请求,则忽略之前的验证异常并使用成功的验证。如果没有后续AuthenticationProvider
提供非空响应或新的AuthenticationException
,则将使用最后收到的AuthenticationException
。
如果没有AuthenticationProvider
返回非空响应,或者表明它可以处理Authentication
,则ProviderManager
将抛出ProviderNotFoundException
。也可以设置父AuthenticationManager
,如果配置的AuthenticationProvider
都不能执行身份验证,也会尝试这样做。
此过程的例外情况是AuthenticationProvider
抛出AccountStatusException
,在这种情况下,将不会查询列表中的其他AuthenticationProvider
。身份验证后,如果它实现了CredentialsContainer
接口,则将从返回的Authentication
对象中清除凭证。可以通过修改eraseCredentialsAfterAuthentication
属性来控制此行为。
身份验证事件发布被委托给配置的AuthenticationEventPublisher
,它默认为不发布事件的空实现,如果想接收事件,则必须设置AuthenticationEventPublisher
,标准实现是DefaultAuthenticationEventPublisher
,它将常见异常映射到事件(在身份验证失败的情况下),并在身份验证成功时发布AuthenticationSuccessEvent
。
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
// 用于验证事件的发布
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
// 用于验证Authentication的AuthenticationProvider列表
private List<AuthenticationProvider> providers = Collections.emptyList();
// 用于访问来自MessageSource的消息
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 父AuthenticationManager
private AuthenticationManager parent;
// 验证之后是否删除凭证
private boolean eraseCredentialsAfterAuthentication = true;
/**
* 使用给定的多个AuthenticationProvider构造一个ProviderManager
*/
public ProviderManager(AuthenticationProvider... providers) {
this(Arrays.asList(providers), null);
}
/**
* 使用给定的AuthenticationProvider列表构造一个ProviderManager
*/
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
/**
* 使用提供的参数构造一个ProviderManager
* providers - 要使用的AuthenticationProvider列表
* parent - 要回退到的父AuthenticationManager
*/
public ProviderManager(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
// 在Properties设置之后检查状态
public void afterPropertiesSet() {
checkState();
}
// 检查状态
private void checkState() {
if (parent == null && providers.isEmpty()) {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
} else if (providers.contains(null)) {
throw new IllegalArgumentException(
"providers list cannot contain null values");
}
}
/**
* 尝试对传递的Authentication对象进行身份验证
* 将连续尝试AuthenticationProvider的列表
* 直到AuthenticationProvider指示它能够验证传递的Authentication对象的类型
* 然后将尝试使用该AuthenticationProvider进行身份验证
* 如果多个AuthenticationProvider支持传递的Authentication对象
* 则第一个能够成功验证Authentication对象的人会确定result
* 成功认证后,不会尝试后续的AuthenticationProvider
* 如果任何支持AuthenticationProvider的身份验证未成功
* 则抛出AuthenticationException
*/
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Authentication实例的类型
Class<? extends Authentication> toTest = authentication.getClass();
// 最后要抛出的异常
AuthenticationException lastException = null;
// 父AuthenticationManager要抛出的异常
AuthenticationException parentException = null;
// 验证结果
Authentication result = null;
// 父AuthenticationManager的验证结果
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
// 遍历AuthenticationProvider列表
for (AuthenticationProvider provider : getProviders()) {
// 如果该AuthenticationProvider实例不支持Authentication实例的类型,则continue
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
// 使用AuthenticationProvider实例验证authentication
result = provider.authenticate(authentication);
if (result != null) {
// 如果验证结果不为null,则填充result
// 并且退出后续的验证
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: 如果身份验证失败是由于无效的帐户状态引起的,避免轮询其他AuthenticationProvider
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
// 如果验证结果为null,并且父AuthenticationManager不为null
// 则使用父AuthenticationManager进行验证
if (result == null && parent != null) {
try {
// 父AuthenticationManager的验证结果赋值给result和parentResult
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
// 验证结果不为null
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// 验证完成
// 从authentication中删除凭证和其他秘密数据
((CredentialsContainer) result).eraseCredentials();
}
// 如果父AuthenticationManager已尝试验证并成功
// 那么它将发布一个AuthenticationSuccessEvent
// 如果父AuthenticationManager已经发布了它
// 此检查可防止重复的AuthenticationSuccessEvent
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent为null(此时result为null),未进行身份验证(引发异常)
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// 如果父AuthenticationManager已尝试但失败
// 则它将发布AbstractAuthenticationFailureEvent
// 如果父AuthenticationManager已发布
// 此检查可防止重复AbstractAuthenticationFailureEvent
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
// 根据authentication准备exception
@SuppressWarnings("deprecation")
private void prepareException(AuthenticationException ex, Authentication auth) {
eventPublisher.publishAuthenticationFailure(ex, auth);
}
/**
* 将身份验证详细信息从源身份验证对象复制到目标身份验证对象,前提是后者还没有
* 参数:
* source - 源身份验证对象
* dest - 目标身份验证对象
*/
private void copyDetails(Authentication source, Authentication dest) {
if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
token.setDetails(source.getDetails());
}
}
// 返回AuthenticationProvider列表
public List<AuthenticationProvider> getProviders() {
return providers;
}
// 设置messages属性
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
// 设置eventPublisher 属性
public void setAuthenticationEventPublisher(
AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
/**
* 设置eraseCredentialsAfterAuthentication属性
* 如果设置为true
* 实现CredentialsContainer接口的结果Authentication
* 将在从authenticate方法返回之前调用其eraseCredentials方法
*/
public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
this.eraseCredentialsAfterAuthentication = eraseSecretData;
}
// 返回eraseCredentialsAfterAuthentication
public boolean isEraseCredentialsAfterAuthentication() {
return eraseCredentialsAfterAuthentication;
}
...
}
Debug分析
项目结构图:
pom.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaven</groupId>
<artifactId>security</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
application.yml
:
spring:
security:
user:
name: kaven
password: itkaven
logging:
level:
org:
springframework:
security: DEBUG
SecurityConfig
(Spring Security
的配置类,不是必须的,因为有默认的配置):
package com.kaven.security.config;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 任何请求都需要进行验证
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
// 记住身份验证
.rememberMe(Customizer.withDefaults())
// 从登陆页进行验证
// 指定支持基于表单的身份验证
.formLogin(Customizer.withDefaults());
}
}
MessageController
(定义接口):
package com.kaven.security.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MessageController {
@GetMapping("/message")
public String getMessage() {
return "hello spring security";
}
}
启动类:
package com.kaven.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
Debug
方式启动应用,访问http://localhost:8080/message
,请求会被重定向到登陆页,需要输入用户名和密码。
身份验证请求会被ProviderManager
实例处理,如下图所示,ProviderManager
实例有两个AuthenticationProvider
,分别为AnonymousAuthenticationProvider
实例和RememberMeAuthenticationProvider
实例,而它的父AuthenticationManager
有一个AuthenticationProvider
,即DaoAuthenticationProvider
实例。
ProviderManager
实例的两个AuthenticationProvider
都不支持该身份验证请求,但它的父AuthenticationManager
的AuthenticationProvider
支持验证该身份验证请求。
父AuthenticationManager
的AuthenticationProvider
会对身份验证请求进行验证,很显然是验证成功了。
身份验证处理AuthenticationManager
介绍与Debug
分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。