0
点赞
收藏
分享

微信扫一扫

Spring Security:身份验证处理AuthenticationManager介绍与Debug分析

兔八哥软件爱分享 2022-01-21 阅读 29

AuthenticationManager

处理Authentication请求,上一篇博客已经介绍了AuthenticationSpring 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

SecurityConfigSpring 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都不支持该身份验证请求,但它的父AuthenticationManagerAuthenticationProvider支持验证该身份验证请求。
在这里插入图片描述
AuthenticationManagerAuthenticationProvider会对身份验证请求进行验证,很显然是验证成功了。
在这里插入图片描述
在这里插入图片描述
身份验证处理AuthenticationManager介绍与Debug分析就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。

举报

相关推荐

0 条评论