Spring Security 源码学习(三): Spring Security认证流程

2022/5/26 1:51:16

本文主要是介绍Spring Security 源码学习(三): Spring Security认证流程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

【参考文章】: Spring Security 认证流程 (写的很形象)

认证功能由 springSecurityFilterChain 中的 UsernamePasswordAuthenticationFilter 实现

认证流程

  1. UsernamePasswordAuthenticationFilter 创建一个未认证的Authentication, 然后交给 AuthenticationManager 进行认证
  2. AuthenticationManager 的默认实现 ProviderManager 管理负责认证的AuthenticationProvider, 然后遍历AuthenticationProvider, 如果这个AuthenticationProvider 支持这种类型的认证, 将未认证信息交给 AuthenticationProvider 处理认证
  3. 认证成功则会返回一个通过认证的Authentication对象,否则抛异常表示认证失败

1. AbstractAuthenticationProcessingFilter(认证入口)

该类下有三个子类,子类都没有重写doFilter(),都是调用父类的doFilter()

  1. ClientCredentialsTokenEndpointFilter
  2. OAuth2ClientAuthenticationProcessingFilter(OAuth2下实现)
  3. UsernamePasswordAuthenticationFilter(默认实现)

doFilter()中调用了子类的attemptAuthentication()进行真正的认证逻辑处理

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
			
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		Authentication authResult;
		try {
			// 调用子类实现的抽象方法,返回一个验证对象
			// 该方法的实现才是真正处理验证逻辑
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			// 验证失败
			logger.error("An internal error occurred while trying to authenticate the user.",failed);
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		catch (AuthenticationException failed) {
			// 验证失败
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		// 验证成功
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
		successfulAuthentication(request, response, chain, authResult);
	}
}

2. UsernamePasswordAuthenticationFilter

  1. 根据用户名和密码生成一个 UsernamePasswordAuthenticationToken 类型的 Authentication
  2. 将 Authentication 交给 ProviderManager 处理
public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	// 默认处理POST方法的 /login请求(这是默认的登录请求URI)
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}

	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		// 只处理POST方法的请求
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);
		if (username == null) {
			username = "";
		}
		if (password == null) {
			password = "";
		}
		username = username.trim();
		// 后续AuthenticationProvider 会根据 authRequest 的 class 类型判断自己是否进行认证
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		setDetails(request, authRequest);
		// 此处 his.getAuthenticationManager() 返回的是 ProviderManager
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}

3. ProviderManager

管理负责认证的 AuthenticationProvider
默认的 providers 只有一个,类型为 AnonymousAuthenticationProvider
默认的 parent 类型为 ProviderManager , 其 providers 为 DaoAuthenticationProvider, 默认情况下最终由 DaoAuthenticationProvider 处理认证

public class ProviderManager implements AuthenticationManager, MessageSourceAware,InitializingBean {
	public Authentication authenticate(Authentication authentication)throws AuthenticationException {

		for (AuthenticationProvider provider : getProviders()) {
                        // 不支持认证, 则跳过
			if (!provider.supports(toTest)) {
				continue;
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					// 有一个 AuthenticationProvider 认证通过则结束
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}

		if (result == null && parent != null) {
			try {
				// 所有的provider都不支持校验, 则由parent进行校验
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
			}
			catch (AuthenticationException e) {
				lastException = parentException = e;
			}
		}

		if (result != null) {
			if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {
				((CredentialsContainer) result).eraseCredentials();
			}
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}
		...省略其他代码
	}
}

4. DaoAuthenticationProvider

处理认证默认配置

  1. DaoAuthenticationProvider 的 authenticate() 继承自父类 AbstractUserDetailsAuthenticationProvider, 自己本身并未实现
  2. 父类中的 authenticate() 调用子类实现的 retrieveUser() 进行认证, 认证成功则返回一个 UserDetails 的实例, 否则认证失败
  3. DaoAuthenticationProvider 的 retrieveUser() 通过 UserDetailsService 的 loadUserByUsername() 方法获取 UserDetails 的实例
  4. UserDetailsService 就是我们一般实现的接口,并在 WebSecurityConfigurerAdapter 的实现类中进行配置


这篇关于Spring Security 源码学习(三): Spring Security认证流程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程