全网最详解Spring Security登录原理(带你看源代码)

使用的jar和版本:
springboot:2.4.2

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
            <version>2.2.4.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframework.socialgroupId>
            <artifactId>spring-social-securityartifactId>
            <version>1.1.6.RELEASEversion>
        dependency>

0.security过滤器链总概括

下图为security过滤器链的流程图
全网最详解Spring Security登录原理(带你看源代码)_第1张图片

0 对于上图的讲解

  1. 登录检验过滤器(上图的绿色模块):根据上图所示配置了所有的请求都需要身份认证,那么,此拦截器就会检测当前请求是否经过了绿色过滤器的认证,即检查标记,实际上,这一段配置可以写的非常复杂,比如可以配置某类请求只有VIP用户才允许访问等,这些配置都会被拦截器读取,拦截器会根据这些配置做判断。如果判断通过,那么访问到具体API,如果不过,根据不过的原因,会抛出不同的异常。
    比如在当前配置中,配置了所有请求都需要身份认证,那么,如果当前请求没有认证,拦截器就会抛出一个用户没有经过身份认证异常。如果配置文件中配置了当前请求只有VIP用户才能访问,而用户在之前虽然验证了,但是并不是VIP用户,那么就会抛出用户权限不足异常,在异常抛出之后,就会被固定在拦截器前面一环的ExceptionTranslationFilter所捕获,这两个过滤器是固定在过滤器最终的两环之上。
    ExceptionTranslationFilter这个过滤器最主要的作用就是用来捕获守门人所抛出的异常,然后根据抛出的异常,作出相应的处理,比如说,如果是因为没有登录抛出的异常,那么就会根据配置文件中的配置,引导用户完成认证,比如,配置文件中配置了formLogin方式认证,那么该过滤器就会引导用户到默认的认证页面,如果配置了httpBasic方式,那么就会让浏览器弹出一个窗口,让用户进行认证。
    至此,即为SpringSecurity整个核心业务的基本原理,整个框架提供的业务和特型,都是经过当前过滤器链实现的。在之后的章节中,会讲解如何使用手机验证码,或者微信qq等第三方登录,实际上,都是在这个过滤器链上加入这种绿色的拦截器,来支持不同的身份认证功能逻辑,在实际的业务中,过滤器链上过滤器链上过滤器不止这三种,还会有其他很多种,一般一个普通的应用都会有10几个这个过滤器,在后续的章节中,会讲解这些过滤器,目前,在讲解SpringSecurity基本原理的过程中,只需要知道有这三种过滤器即可。
    注意,在拦截器上,这些绿色的拦截器是可以通过配置文件决定使用或者不是用的,但是其他两种过滤器,是肯定会出现在框架中,并且肯定是在最后的两个环节上。位置不可更改,也不能在过滤器链上去掉。

一些解释和说明
(1)Authentication(身份验证)对象:是Spring Security用来描述当前用户的相关信息的一个对象。而对于不同的登录方式,会实现一种该组件,实际上就是个pojo。

  1. 用户名和密码被获取并组合到UsernamePasswordAuthenticationToken的实例中(Authentication接口的实例)。
  2. token令牌被传递到AuthenticationManager的实例进行验证。
  3. 身份验证成功时,AuthenticationManager将返回一个完全填充的身份验证实例。
  4. 安全上下文是通过调用SecurityContextHolder.getContext().setAuthentication(…),传入返回的身份验证对象。

查看它的源代6个方法,后面的流程有用到。

Authentication接口

public interface Authentication extends Principal, Serializable {
     '
    //授予委托人的权限集合。例如登录人是员工则是员工的权限,登录者是用户则是用户的权限。
	Collection<? extends GrantedAuthority> getAuthorities();
	//证明主体正确的凭据,通常是密码
	Object getCredentials();
	//存储有关身份验证请求的其他详细信息。例如:IP地址,证书序列号等。
	Object getDetails();
	//被认证主体的身份
	Object getPrincipal();
	//令牌是否通过身份验证
	boolean isAuthenticated();
	//设置身份验证令牌是否受信任
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

(2)idea按ctrl+n可以搜索所有的源代码

开始源代码的流程详解

1.AuthenticationFilter(梦开始的地方)

  • 认证过滤器,每当容器支持一种登录方式的时候,就需要在主链中添加一种该组件。就是上面流程图绿色模块的过滤器链。

只要其中一个登录过滤器链通过,其它的相同类型的过滤器直接通过,不再检验。这也好理解,因为我们登录账号的时候只需要使用一种登录方式进行检验就行。

例如:
BasicAuthenticationFilter过滤器:登录方式默认弹出一个输入弹窗
UsernamePasswordAuthenticationFilter过滤器:为最常常见的账号密码登录方式
除此之外还有邮箱登录、短信登录等等。
本文主要讲解账号密码登录的方式

2.UsernamePasswordAuthenticationFilter

  • 将前端传回来的usernamepassword打包,传给providerManager处理。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
     
    //这个类设置许多的默认值,后期都可以修改为我们需要的
    //在这里默认账号为username,密码为password,默认请求路径和方式分别为/login,POST
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
	//默认的登录访问地址
	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
			"POST");

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	//仅仅接受post请求
	private boolean postOnly = true;

	public UsernamePasswordAuthenticationFilter() {
     
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
	}

	public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
     
		super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
	}
	//接受request请求 和 response响应
	@Override
	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());
		}
		//获取用户名和密码
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		//构造UsernamePasswordAuthenticationToken对象
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		//为details属性赋值
		setDetails(request, authRequest);
		// 调用authenticate方法进行校验,AuthenticationManager接口的ProviderManager实现类进行校验
		return this.getAuthenticationManager().authenticate(authRequest);
	}

构造UsernamePasswordAuthenticationToken对象
传入获取到的用户名和密码,而用户名对应UPAT对象中的principal属性,而密码对应credentials属性。

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
     

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final Object principal;

	private Object credentials;
	//UsernamePasswordAuthenticationToken对象构造器
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
     
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		//默认指定不应信任身份验证令牌
		setAuthenticated(false);
	}
	//代码省略
	//。。。。
	//。。。。

3.AuthenticationManager是用来管理AuthenticationProvider的接口

  • AuthenticationManager是一个用来处理身份验证请求的顶级接口,它自己不直接处理认证请求,而是委托给其所配置的Authentication,AuthenticationManager的实现有很多,通常使用ProviderManager对认证请求链进行管理。

通常,一个 AuthenticationManager(或更常见的一个 AuthenticationProvider)将在成功进行身份验证之后返回一个不变的身份验证令牌,在这种情况下,令牌可以安全地返回 true此方法。返回true将提高性能,因为AuthenticationManager不再需要为每个请求调用。

//AuthenticationManager接口
public interface AuthenticationManager {
     
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

//AuthenticationProvider接口
public interface AuthenticationProvider {
     
    Authentication authenticate(Authentication var1) throws AuthenticationException;
i
    boolean supports(Class<?> var1);
}

ProviderManager实现类

  • ProviderManager主要是对AuthenticationProvider链进项管理

通过查找后进入,然后使用ctrl+H组合键查看它的继承关系,找到ProviderManager实现类,它实现了AuthenticationManager接口,它有个重要的authenticate方法。主要是根据传入的token遍历容器中的所有的provider,找到对应的适配器,并调用适配器去处理当前token。

如果有多个AuthenticationProvider支持传递的 Authentication对象,则第一个能够成功验证该Authentication对象的对象将确定 result,从而覆盖AuthenticationException 早期支持AuthenticationProviders抛出的任何可能(提高性能)。验证成功后,AuthenticationProvider将不会尝试后续的。如果通过任何支持均未通过身份验证, AuthenticationProvider则将抛出最后一个抛出的错误 AuthenticationException。

	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
     
	//代码省略
	//。。。。
	//。。。。。
	     //整个过程主要就是确定用户当前是登录哪种登录方式而使用对于的登录过滤器进行验证
	    //迭代多个AuthenticationProvider对象(登录验证过滤器)
		for (AuthenticationProvider provider : getProviders()) {
     
			//注意这个supports方法
			if (!provider.supports(toTest)) {
     
				continue;
			}
			if (logger.isTraceEnabled()) {
     
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
     
			    //注意这个authenticate方法
			    //确定Authentcation对象
				result = provider.authenticate(authentication);
				if (result != null) {
     
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
     
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
	//代码省略
	//。。。。
	//。。。。
	}

    /*
    ProviderManager中有一个List用来存储定义的AuthenticationProvider认证实现类
    也可以认为是一个认证处理器链来支持同一个应用中的多个不同身份认证机制
    ProviderManager将会根据顺序来进行验证
    */
	public List<AuthenticationProvider> getProviders() {
     
		return this.providers;
	}

4.AuthenticationProvider接口

  • 就是进行身份认证的接口,它里面有两个方法:authenticate认证方法和supports是否支持某种类型token的方法,通过ctrl+h查看继承关系,找到AbstractUserDetailsAuthenticationProvider抽象类,它实现了AuthenticationProvider接口。

AbstractUserDetailsAuthenticationProvider抽象类

  • 用户认证 和 判断token认证的方法

工作流程:先看缓存中是否有用户,如果没有,调用自实现去找,找到之后,做了后置检测,检测4个标志是否为true,并且判断容器中是否有passwordencoder,如果有,使用加密匹配规则,如果没有,直接用字符串匹配前端以及数据库中的密码。

authenticate认证方法

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
     
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		//如果从缓存中没有获取到UserDetails
		//表示没有登录成功或记录
		//那么它调用retrieveUser方法来获取用户信息UserDetails,同时捕获异常
		if (user == null) {
     
			cacheWasUsed = false;
			try {
     
			//这里的retrieveUser是抽象方法,主要是关注它的子类实现。
			//等下会讲,先观察UserDetails
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
				//代码省略(下面会将)。。。
				//........。。
				//...........
		return createSuccessAuthentication(principalToReturn, authentication, user);
}

supports是否支持某种类型token的方法

public boolean supports(Class<?> aClass) {
     
   //返回UsernamePasswordAuthenticationToken
   //说明它是支持UsernamePasswordAuthenticationToken类型的AuthenticationProvider
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}

UserDetails接口

  • 用户信息UserDetails是个接口,当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的,而在实际项目中账号和密码都是从数据库中查询出来的,所以我们要通过自定义逻辑控制认证逻辑,只需要实现 UserDetailsService 接口即可,点进去查看它的该接口的信息。

发现该封装类一共就7个方法,都是用来表示用户的认证信息的,从上往下,依次为,用户权限列表,密码,用户名,账号本身是否过期,是否锁定,本次登录凭证是否过期,账号本身是否可用。基本上封装了大多数系统所需要的认证信息。如果系统中没有需要验证账号本身是否过期的业务,那么也可以将这些字段永远设置为真,如果为假,那么则认证失败。

public interface UserDetails extends Serializable {
     
	Collection<? extends GrantedAuthority> getAuthorities();//在集合中获取所有权限
	String getPassword();//获取密码
	String getUsername();//获取用户名
	boolean isAccountNonExpired();//是否账号过期
	boolean isAccountNonLocked();//是否账号被锁定
	boolean isCredentialsNonExpired();//凭证(密码)是否过期
	boolean isEnabled();//是否可用
}

继续观察刚才AbstractUserDetailsAuthenticationProvider抽象类的authenticate认证方法中的省略代码,有三个重要的检验用户登录方法和一个检验成功的回调方法:

  1. preAuthenticationChecks.check()
  2. additionalAuthenticationChecks()
  3. postAuthenticationChecks()
  4. createSuccessAuthentication()
//。。。。。。。。。。。接上
		try {
     
            //preAuthenticationChecks.check()方法:检验3个boolean方法
			this.preAuthenticationChecks.check(user);
		    //additionalAuthenticationChecks():用于检测账号和密码(可以点进去查看)
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
				catch (AuthenticationException ex) {
     
			if (!cacheWasUsed) {
     
				throw ex;
			}
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		//postAuthenticationChecks():检验1个Boolean
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
     
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
     
			principalToReturn = user.getUsername();
		}
		//如果上面的检查都通过并且没有异常,表示认证通过,会调用下面的方法:
 	return createSuccessAuthentication(principalToReturn, authentication, user);
	}

preAuthenticationChecks预检查,在最下面的内部类DefaultPreAuthenticationChecks中可以看到,它会检查上面提到的三个boolean方法,即检查账户未锁定、账户可用、账户未过期,如果上面的方法只要有一个返回false,就会抛出异常,那么认证就会失败。下面还有个postAuthenticationChecks.check(user)后检查,在最下面的DefaultPostAuthenticationChecks内部类中可以看到,它会检查密码未过期,如果为false就会抛出异常

		@Override
		public void check(UserDetails user) {
     
			if (!user.isAccountNonLocked()) {
     
				AbstractUserDetailsAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account is locked");
				throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
			}
			if (!user.isEnabled()) {
     
				AbstractUserDetailsAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account is disabled");
				throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
			}
			if (!user.isAccountNonExpired()) {
     
				AbstractUserDetailsAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account has expired");
				throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
			}
		}


		@Override
		public void check(UserDetails user) {
     
			if (!user.isCredentialsNonExpired()) {
     
				AbstractUserDetailsAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account credentials have expired");
				throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
								"User credentials have expired"));
			}
		}

additionalAuthenticationChecks() 是附加检查,允许子类对UserDetails给定的身份验证请求执行返回(或缓存)返回的任何其他检查,它主要是检查用户密码的正确性,如果密码为空或者错误都会抛出异常。通常,子类至少会Authentication.getCredentials()与 进行比较UserDetails.getPassword()。如果需要自定义逻辑来比较UserDetails和/或的 其他属性UsernamePasswordAuthenticationToken,则这些属性也应出现在此方法中。

	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
     
		if (authentication.getCredentials() == null) {
     
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
     
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

createSuccessAuthentication() 如果全部检验通过,创建一个成功的Authentication对象,查看该方法的方法体,发现是通过构造方法实例化对象UsernamePasswordAuthenticationToken时,调用的是三个参数的构造方法,返回一个成功的认证令牌。
三个参数说明:

  1. principal:应该是返回对象的主体(由isForcePrincipalAsString()方法定义)
  2. authentication:提交给提供商进行验证。需要Authentication对象的密码和其它身份信息
  3. user:需要UserDetails对象的集合中获取所有权限

UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication
所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),然后生成的Authentication会被交由AuthenticationManager来进行管理而AuthenticationManager管理一系列的AuthenticationProvider,而每一个Provider都会通–UserDetailsServiceUserDetail来返回一个以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication。
在下面的代码中通过new出一个新UsernamePasswordAuthenticationToken 的对象并使用setDetails(authentication.getDetails()):向新的对象设置authentication对象的有关身份验证请求的其他详细信息,这些可能是IP地址,证书序列号等。到这里就表示认证通过了。

	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());
		this.logger.debug("Authenticated user");
		return result;
	}

5.DaoAuthenticationProvider类

  • 最终查询数据库的类,收集容器中userdetail的实现,调用loaduserbyusername方法,返回数据库中的用户信息。

下面是获取用户信息UserDetails的retrieveUser方法

  1. 参数:username -要检索的用户名 | authentication-身份验证请求,子类可能 需要执行基于绑定的检索UserDetails
  2. 返回值:用户信息(从不null-应当抛出异常)

它是调用了getUserDetailsService先获取到UserDetailsService对象,通过调用UserDetailsService对象的loadUserByUsername方法根据对应的username用户名来获取用户信息UserDetails

@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
     
		prepareTimingAttackProtection();
		try {
     
		//获取用户信息UserDetails
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
     
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		//代码省略
		//。。。。
		//。。。。。

找到UserDetailsService,发现它是一个接口,查看继承关系,有很多实现,都是spring-security提供的实现类,并不满足我们的需要,我们想自己制定获取用户信息的逻辑,所以我们可以实现这个接口。比如从我们的数据库中查找用户信息

public interface UserDetailsService {
     
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

覆盖安全框架的默认登录, 只要继承 UserDetailsService接口并被容器监控到,就可以覆盖掉默认方法。下面为自定义登录的实现类。

@Service
@Slf4j
public class UserDeatailServiceImpl implements UserDetailsService {
     
    @Autowired
    private UsersMapper usersMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
     
        log.debug("登入的用户:{}",s);

        Users users=new Users();
        users.setUsername(s);

        Users loginUser = usersMapper.selectOne(users);

        if (loginUser==null){
     
            throw new PassPortException("用户名或者密码不匹配");
        }
        //默认全部为true保证通过
        return new User(loginUser.getUsername(), loginUser.getPassword(),
                true, true, true, true,
                //身份权限名可以自定义,按照这里写比较规范
                AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN"));
    }
}

6.AbstractAuthenticationProcessingFilter

  • 所有认证过滤器的父类,将所有认证的逻辑共有的部分抽取到父类,前置检测当前请求是否有当前过滤器处理的,后置判断当前认证是否成功:
  1. 如果成功,那么使用认证成功处理器处理,并且将登陆成功的依据交给rememberMeService。

  2. 如果失败(登录流程一次)调用登录失败处理器,并且清空securityContext,再调用rememberMeService的loginfail

接下来看它的源代码

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
     
		//第三种情况:返回Null,表示身份验证过程未完成。
		if (!requiresAuthentication(request, response)) {
     
			chain.doFilter(request, response);
			return;
		}
		try {
     
		    //UsernamePasswordAuthenticationFilter类的attemptAuthentication方法继承了AbstractAuthenticationProcessingFilter类
		    //返回一个经过身份验证的用户令牌;如果身份验证不完整,则返回null。
            //或是 抛出异常AuthenticationException:表示身份验证失败。
			Authentication authenticationResult = attemptAuthentication(request, response);
			//第三种情况:返回Null,表示身份验证过程未完成。
			if (authenticationResult == null) {
     
				return;
			}
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			if (this.continueChainBeforeSuccessfulAuthentication) {
     
				chain.doFilter(request, response);
			}
			//第一种情况:一个认证返回的对象,调用登录成功的逻辑方法
			successfulAuthentication(request, response, chain, authenticationResult);
		}
		catch (InternalAuthenticationServiceException failed) {
     
			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
			//第二种情况:验证中出现一次,调用登录失败的逻辑的方法
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
     
			//第二种情况:验证中出现一次,调用登录失败的逻辑的方法
			unsuccessfulAuthentication(request, response, ex);
		}
	}

    //登录成功的逻辑方法
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
     
			//从当前执行的线程中获取对象信息并更改当前已认证的主体
		SecurityContextHolder.getContext().setAuthentication(authResult);
		if (this.logger.isDebugEnabled()) {
     
			this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
		}
		//将登陆成功的依据交给rememberMeService
		this.rememberMeServices.loginSuccess(request, response, authResult);
		if (this.eventPublisher != null) {
     
			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
		}
		//成功处理器,可以自定义
		this.successHandler.onAuthenticationSuccess(request, response, authResult);
	}

     //登录失败的逻辑方法
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
     
		//清空securityContext
		SecurityContextHolder.clearContext();
		this.logger.trace("Failed to process authentication request", failed);
		this.logger.trace("Cleared SecurityContextHolder");
		this.logger.trace("Handling authentication failure");
		//记住我的功能 表示保存失败
		this.rememberMeServices.loginFail(request, response);
		//失败处理器,可以自定义
		this.failureHandler.onAuthenticationFailure(request, response, failed);
	}

7. 个人总结

1.认证的检验的简单流程图
全网最详解Spring Security登录原理(带你看源代码)_第2张图片

2.一些类的讲解
全网最详解Spring Security登录原理(带你看源代码)_第3张图片

3.大概代码流程图
全网最详解Spring Security登录原理(带你看源代码)_第4张图片

你可能感兴趣的:(spring框架,springboot,java,spring)