Loading

SpringSecurity系列学习(三):认证流程和源码解析

系列导航

SpringSecurity系列

SpringSecurityOauth2系列

认证流程和源码解析

有小伙伴看到这里就会说了:说好的编码呢?说好的编码呢?为什么还要看原理啊?憋嗦话,上号!

现在开始编码,你必不能启动项目!

咳咳

先打基础打基础,我们学习SpringSecurity不是为了写一个Hello world就阔以了,最后是为了把SpringSecurity用在项目中,现在理解的原理越多,后面出现bug才好定位修复!

SpringSecurity核心组件

在分析认证流程之前,我们先来看一下一些SpringSecurity的一些概念

SecurityContextHolder:是一个工具类,它提供了对安全上下文的访问。默认情况下,它使用一个ThreadLocal对象来存储安全上下文,这意味着它是线程安全的。

SecurityContext:是用来存储当前认证的用户的详细信息。

Authentication:

  • 存储了当前用户(与应用程序交互的主体)的详细信息
  • Principal可以理解为用户的信息(比较简单的情况下,有可能是用户名)
  • Credentials可以理解为密码
  • Authorities可以理解为权限

Authentication是Spring认证体系的核心元素,Spring Security内建了很多具体的派生类,比如最常见的用于用户名/密码登录场景的UsernamePasswordAuthenticationToken

我们在第一章最后自定义过滤器的时候,返回了一个Authentication的实现类,那个实现类就是UsernamePasswordAuthenticationToken

{
  "authenticated": true,
  "authorities": [
    {
      "authority": "ROLE_ADMIN"
    },
    {
      "authority": "ROLE_USER"
    }
  ],
  "details": {
    "remoteAddress": "127.0.0.1"
  },
  "name": "user",
  "principal": {
    "accountNonExpired": true,
    "accountNonLocked": true,
    "authorities": [
      {
        "$ref": "$.authorities[0]"
      },
      {
        "$ref": "$.authorities[1]"
      }
    ],
    "credentialsNonExpired": true,
    "enabled": true,
    "username": "user"
  }
}

认证流程分析

认证流程

认证流程如图所示

  1. 请求进入认证过滤器(AuthenticationFilter),获取用户名和密码,构建成UserNamepasswordAuthenticationToken,但是这个对象没有被完全的初始化,因为这个时候你只加入了用户名与密码,一般这个角色里面还有角色列表和是否认证等信息。
  2. 认证过滤器(AuthenticationFilter)将部分初始化的UserNamepasswordAuthenticationToken传递给AuthenticationManager,其也是一个接口类,用户真正执行认证的类,其中有一个AuthenticationProvider集合。一个AuthenticationProvider就是一种具体的认证机制,比如有时候我们需要从数据库中读取用户信息进行认证,有时候我们需要进行多因子认证,比如短信,邮箱等。执行认证的时候AuthenticationManager就去遍历AuthenticationProvider集合,判断是否支持当前认证方式,如果支持,则调用当前AuthenticationProvider的认证方法进行认证
  3. 如果是数据库认证的AuthenticationProvider来说,就会去调用UserDetailsService去获取用户信息进行认证。
  4. 成功了,就进行返回,返回到认证过滤器(AuthenticationFilter)的时候,就将Authentication对象,也就是初始化之后的UserNamepasswordAuthenticationToken(其实是重新构造了一个,使用了不同的构造函数),放到SecuityContext当中。

认证源码解析

UsernamePasswordAuthenticationFilter

先看主要负责认证的过滤器UsernamePasswordAuthenticationFilter,有删减,注意注释。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
{
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
	private String usernameParameter = "username";
	private String passwordParameter = "password";
	private Boolean postOnly = true;
    
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
    
    
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse
	response) throws AuthenticationException {
		//必须为POST请求
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " +
			request.getMethod());
		} else {
			String username = this.obtainUsername(request);
			String password = this.obtainPassword(request);
			if (username == null) {
				username = "";
			}
			if (password == null) {
				password = "";
			}
			username = username.trim();
			//将填写的用户名和密码封装到了UsernamePasswordAuthenticationToken中
			UsernamePasswordAuthenticationToken authRequest = new
			UsernamePasswordAuthenticationToken(username, password);
			this.setDetails(request, authRequest);
			//调用AuthenticationManager对象实现认证
			return this.getAuthenticationManager().authenticate(authRequest);
		}
	}
}

AuthenticationManager

由上面源码得知,真正认证操作在AuthenticationManager里面!
然后看AuthenticationManager的实现类ProviderManager

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
	private static final Log logger = LogFactory.getLog(ProviderManager.class);
	private AuthenticationEventPublisher eventPublisher;
	private List<AuthenticationProvider> providers;
	protected MessageSourceAccessor messages;
	private AuthenticationManager parent;
	private Boolean eraseCredentialsAfterAuthentication;
    
    
	//注意AuthenticationProvider这个对象,SpringSecurity针对每一种认证,什么qq登录啊,
	//用户名密码登陆啊,微信登录啊都封装了一个AuthenticationProvider对象。
	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, (AuthenticationManager)null);
	}
	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();
		Iterator var8 = this.getProviders().iterator();
		//循环所有AuthenticationProvider,匹配当前认证类型。
		while(var8.hasNext()) {
			AuthenticationProvider provider = (AuthenticationProvider)var8.next();
			if (provider.supports(toTest)) {
				if (debug) {
					logger.debug("Authentication attempt using " +
					provider.getClass().getName());
				}
				try {
					//找到了对应认证类型就继续调用AuthenticationProvider对象完成认证业务。
					result = provider.authenticate(authentication);
					if (result != null) {
						this.copyDetails(authentication, result);
						break;
					}
				}
				catch (AccountStatusException var13) {
					this.prepareException(var13, authentication);
					throw var13;
				}
				catch (InternalAuthenticationServiceException var14) {
					this.prepareException(var14, authentication);
					throw var14;
				}
				catch (AuthenticationException var15) {
					lastException = var15;
				}
			}
		}
		if (result == null && this.parent != null) {
			try {
				result = parentResult = this.parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException var11) {
			}
			catch (AuthenticationException var12) {
				parentException = var12;
				lastException = var12;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && result instanceof
			CredentialsContainer) {
				((CredentialsContainer)result).eraseCredentials();
			}
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		} else {
			if (lastException == null) {
				lastException = new
				ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new
				Object[]{toTest.getName()
			}
			, "No AuthenticationProvider found for {0}"));
		}
		if (parentException == null) {
			this.prepareException((AuthenticationException)lastException, authentication);
		}
		throw lastException;
	}
}
}

AbstractUserDetailsAuthenticationProvider

咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
	private PasswordEncoder passwordEncoder;
	private volatile String userNotFoundEncodedPassword;
	private UserDetailsService userDetailsService;
	private UserDetailsPasswordService userDetailsPasswordService;
    
	protected final UserDetails retrieveUser(String username,
	UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		this.prepareTimingAttackProtection();
		try {
			//重点来了!主要就在这里了!
			//UserDetails就是SpringSecurity自己的用户对象。
			//this.getUserDetailsService()其实就是得到UserDetailsService的一个实现类
			//loadUserByUsername里面就是真正的认证逻辑
			//也就是说我们可以直接编写一个UserDetailsService的实现类,告诉SpringSecurity就可以了!
			//loadUserByUsername方法中只需要返回一个UserDetails对象即可
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			//若返回null,就抛出异常,认证失败。
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException("UserDetailsService returned
null, which is an interface contract violation");
			} else {
				//若有得到了UserDetails对象,返回即可。
				return loadedUser;
			}
		}
		catch (UsernameNotFoundException var4) {
			this.mitigateAgainstTimingAttack(authentication);
			throw var4;
		}
		catch (InternalAuthenticationServiceException var5) {
			throw var5;
		}
		catch (Exception var6) {
			throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
		}
	}
}

authenticate返回值

上面不是说到返回了一个
UserDetails对象对象吗?跟着它,就又回到了AbstractUserDetailsAuthenticationProvider对象中authenticate方法的最后一行了。

public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
    
	public Authentication authenticate(Authentication authentication) throws
	AuthenticationException {
        
		//最后一行返回值,调用了createSuccessAuthentication方法,此方法就在下面!
		return this.createSuccessAuthentication(principalToReturn, authentication, user);
	}
    
	//咿!?怎么又封装了一次UsernamePasswordAuthenticationToken,开局不是已经封装过了吗?
	protected Authentication createSuccessAuthentication(Object principal, Authentication
	authentication, UserDetails user) {
		//那就从构造方法点进去看看,这才干啥了。
		UsernamePasswordAuthenticationToken result = new
		UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),
		this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		return result;
	}
}

UsernamePasswordAuthenticationToken

来到UsernamePasswordAuthenticationToken对象发现里面有两个构造方法

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
	private static final long serialVersionUID = 510L;
	private final Object principal;
	private Object credentials;
    
	//认证成功前,调用的是这个带有两个参数的。
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super((Collection)null);
		this.principal = principal;
		this.credentials = credentials;
		this.setAuthenticated(false);
	}
    
	//认证成功后,调用的是这个带有三个参数的。
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
		//看看父类干了什么!
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true);
	}
}

AbstractAuthenticationToken

再点进去super(authorities)看看:

public abstract class AbstractAuthenticationToken implements Authentication,
CredentialsContainer {
	private final Collection<GrantedAuthority> authorities;
	private Object details;
	private Boolean authenticated = false;
    
	public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
		//这是两个参数那个分支!
		if (authorities == null) {
			this.authorities = AuthorityUtils.NO_AUTHORITIES;
		} else {
			//三个参数的,看这里!
			Iterator var2 = authorities.iterator();
			//原来是多个了添加权限信息的步骤
			GrantedAuthority a;
			do {
				if (!var2.hasNext()) {
					ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size());
					temp.addAll(authorities);
					this.authorities = Collections.unmodifiableList(temp);
					return;
				}
				a = (GrantedAuthority)var2.next();
			}
			while(a != null);
			//若没有权限信息,是会抛出异常的!
			throw new IllegalArgumentException("Authorities collection cannot contain any null
elements");
		}
	}
}

由此,咱们需要牢记自定义认证业务逻辑返回的UserDetails对象中一定要放置权限信息啊!

现在可以结束源码分析了吧?先不要着急!

咱们回到最初的地方UsernamePasswordAuthenticationFilter,你看,这可是个过滤器,咱们分析这么久,都没提到doFilter方法,你不觉得心里不踏实?

可是这里面也没有doFilter呀?那就从父类找!

AbstractAuthenticationProcessingFilter

点开AbstractAuthenticationProcessingFilter,删掉不必要的代码!

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
    
	//doFilter在此!
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws
	IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest)req;
		HttpServletResponse response = (HttpServletResponse)res;
		if (!this.requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
		} else {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Request is to process authentication");
			}
			Authentication authResult;
			try {
				authResult = this.attemptAuthentication(request, response);
				if (authResult == null) {
					return;
				}
                
				this.sessionStrategy.onAuthentication(authResult, request, response);
			}
			catch (InternalAuthenticationServiceException var8) {
				this.logger.error("An internal error occurred while trying to authenticate the
user.", var8);
				this.unsuccessfulAuthentication(request, response, var8);
				return;
			}
			catch (AuthenticationException var9) {
				this.unsuccessfulAuthentication(request, response, var9);
				return;
			}
                                  
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
			this.successfulAuthentication(request, response, chain, authResult);
		}
	}
                                  
	protected Boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse
	response) {
		return this.requiresAuthenticationRequestMatcher.matches(request);
	}
                                  
	//成功走successfulAuthentication
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse
	response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Authentication success. Updating SecurityContextHolder to
contain: " + authResult);
		}
		//认证成功,将认证信息存储到SecurityContext中!
		SecurityContextHolder.getContext().setAuthentication(authResult);
		//登录成功调用rememberMeServices
		this.rememberMeServices.loginSuccess(request, response, authResult);
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(new
			InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
		}
                              
		this.successHandler.onAuthenticationSuccess(request, response, authResult);
	}
                              
	//失败走unsuccessfulAuthentication
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse
	response, AuthenticationException failed) throws IOException, ServletException {
		SecurityContextHolder.clearContext();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Authentication request failed: " + failed.toString(), failed);
			this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
			this.logger.debug("Delegating to authentication failure handler " +
			this.failureHandler);
		}
		this.rememberMeServices.loginFail(request, response);
		this.failureHandler.onAuthenticationFailure(request, response, failed);
	}
}

可见AbstractAuthenticationProcessingFilter这个过滤器对于认证成功与否,做了两个分支,成功执行
successfulAuthentication,失败执行unsuccessfulAuthentication
successfulAuthentication内部,将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是常见的“记住我”功能!

posted @ 2021-09-27 16:36  硝酸铜  阅读(1189)  评论(0编辑  收藏  举报