Spring Security学习(二)认证管理器

4 篇文章 0 订阅
3 篇文章 0 订阅

前文分析到 UsernamePasswordAuthenticationFilter的时候,可以发现最终过滤器还是把用户登陆信息交给了认证管理器进行认证,如下面代码所示:

this.getAuthenticationManager().authenticate(authRequest);

本文继续分析认证过程

AuthenticationManager

AuthenticationManager 是一个抽象的认证接口,只定义了一个方法:

Authentication authenticate(Authentication authentication)	throws AuthenticationException;

其默认实现是 ProviderManager, ProviderManager 使用一组 AuthenticationProvider 来完成对一个认证令牌对象authentication的认证。

private List<AuthenticationProvider> providers;

ProviderManager

ProviderManager 的认证逻辑是遍历所有支持该认证令牌对象参数 authentication 的 AuthenticationProvider,找到第一个能成功认证的并返回填充更多信息的 authentication 对象,具体代码如下:

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();

		// 遍历当前认证提供者列表,找到和当前 authentication 匹配的
		for (AuthenticationProvider provider : getProviders()) {
			// 判断是否支持当前认证方式
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				// 使用当前的provider进行认证
				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) {
			// 捕获此异常认证不会终止,会继续执行下面的认证
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			// 如果当前认证返回结果为空,则调用类初始化的上一级认证方式
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
		// 擦除认证中的凭证信息
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

// 发布认证失败事件
private void prepareException(AuthenticationException ex, Authentication auth) {
		eventPublisher.publishAuthenticationFailure(ex, auth);
	}

	// 采纳当前认证结果
	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
			AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;

			token.setDetails(source.getDetails());
		}
	}

根据以上认证过程可以看出实际认证还是不在providerManager中,他只是对系统中的 AuthenticationProvider进行统一的管理和应用。真正执行认证的还是AuthenticationProvider。

AuthenticationProvider

AuthenticationProvider 是抽象了认证提供者的接口

spring security 提供了一些AuthenticationProvide的默认实现,比如RemoteAuthenticationProvider(远程认证), DaoAuthenticationProvider (表单认证), LdapAuthenticationProvider(ldap认证) 等等。

重点学习最常用的DaoAuthenticationProvider ;

首先是父类AbstractUserDetailsAuthenticationProvider

...

// 实现了AuthenticationProvider  中的认证方法authenticate
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
			// 获取用户
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException notFound) {
				logger.debug("User '" + username + "' not found");

				if (hideUserNotFoundExceptions) {
					throw new BadCredentialsException(messages.getMessage(
							"AbstractUserDetailsAuthenticationProvider.badCredentials",
							"Bad credentials"));
				}
				else {
					throw notFound;
				}
			}

			Assert.notNull(user,
					"retrieveUser returned null - a violation of the interface contract");
		}

		try {
			preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			if (cacheWasUsed) {
				// There was a problem, so try again after checking
				// we're using latest data (i.e. not from the cache)
				cacheWasUsed = false;
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
				preAuthenticationChecks.check(user);
				additionalAuthenticationChecks(user,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			else {
				throw exception;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
		// 缓存
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
...



public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	... 

	// ~ Methods
	// ========================================================================================================

	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}

		String presentedPassword = authentication.getCredentials().toString();

		// 比较凭证
		if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			logger.debug("Authentication failed: password does not match stored value");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}
	}

	protected void doAfterPropertiesSet() {
		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
	}

	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);
		}
	}

	@Override
	protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
			// 判断凭证是否加密
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
		if (upgradeEncoding) {
		// 如果凭证已加密,则将认证请求提供的凭证进行相同的加密
			String presentedPassword = authentication.getCredentials().toString();
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}
...
}

上面代码中的getUserDetailsService即是获取再项目启动时配置的userDetailService,我们可以自定义实现UserDetailService接口,完成自己系统的用户校验。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Security 认证失败的原因可能有很多,下面列举一些常见的情况: 1. 用户名或密码错误:请检查用户输入的用户名和密码是否正确。 2. 认证服务器配置错误:请检查认证服务器的 URL 地址是否正确,以及服务器是否正在运行。 3. 证书问题:如果使用 HTTPS 进行认证,请确保服务器证书是有效的。 4. 访问限制:请检查是否存在 IP 地址或用户组限制,限制了该用户的访问权限。 5. 应用程序配置错误:请检查您的应用程序的 Spring Security 配置是否正确,是否启用了正确的认证方法。 如果仍然无法解决问题,建议查看应用程序和服务器的日志,以确定问题的具体原因。 ### 回答2: SpringSecurity认证可能失败的原因有很多,以下列举了一些可能的原因: 1. 用户名或密码错误:在登录时输入的用户名或密码与数据库中存储的用户信息不匹配,导致认证失败。此时需要确认输入的凭证信息是否正确,或者检查数据库中的用户信息是否准确。 2. 密码加密算法不匹配:SpringSecurity默认使用BCrypt算法对用户密码进行加密存储,如果数据库中的存储密码与用户输入的密码加密算法不匹配,会导致认证失败。此时可以尝试将数据库中的密码重新使用BCrypt算法进行加密。 3. 用户被锁定:在一些情况下,系统会对用户进行锁定,例如连续登录失败超过一定次数、账户过期等。当用户被锁定时,即使凭证信息正确,也会导致认证失败。此时需要检查用户是否被锁定,并解除锁定状态。 4. 认证过期:在一些场景下,认证信息可能会设置过期时间,当认证信息过期时,会导致认证失败。此时需要重新进行认证,或者延长认证信息的有效期。 5. 认证方式不匹配:SpringSecurity支持多种认证方式,例如表单认证、基于HTTP的认证、OAuth 2.0等。如果使用的认证方式与配置不匹配,也会导致认证失败。此时需要检查认证方式的配置,并确保与实际使用的方式相符。 以上仅列举了一些可能导致SpringSecurity认证失败的原因,具体失败原因需要根据实际情况进行分析和排查。可以通过查看错误日志、调试代码等方式来定位和解决认证失败的问题。 ### 回答3: Spring Security认证失败可能有多种原因。以下是一些可能导致认证失败的常见原因: 1. 用户名或密码错误:最常见的原因之一是用户提供的用户名或密码与存储在身份验证服务器上的凭据不匹配。在这种情况下,用户需要检查他们输入的凭据是否正确。 2. 用户账号被锁定:有时用户账号可能会被锁定,这可能是由于多次连续的认证失败引起的。在这种情况下,用户可能需要联系系统管理员以解锁账户。 3. 凭据过期或失效:用户的凭据(例如密码)可能已经过期或不再有效。这可能是由于系统要求用户定期更改密码或由于安全问题而导致的。用户需要按照系统要求更改凭据或联系系统管理员以获得有效的凭据。 4. 认证过程配置错误:如果Spring Security配置不正确,可能导致认证失败。这可能涉及到用户提供的凭据无法与配置文件中的凭据验证机制匹配,或者用户权限不正确等问题。用户需要检查Spring Security配置以确保准确性。 5. 服务器问题:在某些情况下,认证失败可能是由于身份验证服务器或相关服务的故障引起的。这可能意味着用户需要等待服务器恢复正常,或者需要联系系统管理员以解决服务器问题。 用户在遇到认证失败时应该首先检查自己提供的凭据是否正确,并尝试解决可能的身份验证问题。如果问题仍然存在,用户应该与系统管理员或支持团队联系以获取进一步的帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值