SpringSecurity原理解析(二):认证流程

1、SpringSecurity认证流程包含哪几个子流程?

      1)账号验证

      2)密码验证

      3)记住我—>Cookie记录

      4)登录成功—>页面跳转

2、UsernamePasswordAuthenticationFilter

      在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤

      器中实现的,UsernamePasswordAuthenticationFilter 继承于 

      AbstractAuthenticationProcessingFilter 这个父类。

      当请求进来时,在doFilter 方法中会对请求进行拦截,判断请求是否需要认证,若不需要

      认证,则放行;否则执行认证逻辑;

              1

               

      注意:UsernamePasswordAuthenticationFilter 类中是没有 doFilter 方法的,doFilter

      方法是继承自父类 UsernamePasswordAuthenticationFilter 的。

            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 {
                //处理认证,然后返回 Authentication 认证对象
                //重点
                authResult = this.attemptAuthentication(request, response);
                //认证失败
                if (authResult == null) {
                    return;
                }
                //认证成功之后注册session
                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 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);
        //处理 remember-me属性
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        //认证成功后,页面跳转
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }


              

      上边的核心代码是下边这一行:

             

       attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,

       attemptAuthentication 方法在子类UsernamePasswordAuthenticationFilter 中实现的。

        attemptAuthentication 方法代码如下:

//认证逻辑
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //如果我们设置了该认证请求只能以post方式提交,且当前请求不是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);
            //设置用户提交的信息到 UsernamePasswordAuthenticationToken  中
            this.setDetails(request, authRequest);
            //getAuthenticationManager():获取认证管理器
            //authenticate:真正处理认证的方法
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

        1、

3、AuthenticationManager

     AuthenticationManager接口中就定义了一个方法authenticate方法,用于处理认证的请求;

     AuthenticationManager 接口定义如下:

public interface AuthenticationManager {

    //处理认证请求
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

    

      在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的

      authenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers

      。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调

      用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。

      如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证

      成功则为认证成功。

      authenticate 方法定义如下:

             

//执行认证逻辑
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
        //获取 Authentication 对象的类型
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

        //getProviders():获取系统支持的各种认证方式,如:QQ、微信、微博等等
		for (AuthenticationProvider provider : getProviders()) {
            //判断当前的 provider认证处理器 是否支持当前请求的认证类型,若不支持,则跳过
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}
            //执行到这里说明当前认证处理器支持当前请求的认证,

			try {
                //执行认证操作
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				//。。。。。省略 。。。。。
			}
			catch (InternalAuthenticationServiceException e) {
				//。。。。。省略 。。。。。
			}
			catch (AuthenticationException e) {
				//。。。。。省略 。。。。。
			}
		}

        //如果循环结束后还没找到支持当前请求的认证处理器provider ,且父类不为空,则
        //尝试调用父类的认证方法进行认证处理
		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				//。。。。。省略 。。。。。
			}
			catch (AuthenticationException e) {
				//。。。。。省略 。。。。。
			}
		}

        //清空密码凭证
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}

			//
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

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

		//
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}

       在上边的代码中,我们重点看的是下边这一行:

              result = provider.authenticate(authentication);

       因为是用户认证,所以这里authenticate方法走是AbstractUserDetailsAuthenticationProvider

       类中的实现,

      AbstractUserDetailsAuthenticationProvider.authenticate 方法定义如下所示:

//认证操作
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

		// 获取提交的账号
		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) {
				//。。。。。省略 。。。。。
			}

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

		try {
            //如果账号存在,即账号认证成功,则这里就开始密码认证
            //密码校验前的前置检查,检查账号是否过期、是否锁定等
			preAuthenticationChecks.check(user);
            //密码校验
            //user: 数据库中的数据
            //authentication: 表单提交的数据
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException exception) {
			//。。。。。省略 。。。。。
		}

        //检查凭证是否过期
        postAuthenticationChecks.check(user);
       
        //将用户保存到缓存中
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

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

		return createSuccessAuthentication(principalToReturn, authentication, user);
}

//创建具体的 Authentication 对象
protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		
		// user.getAuthorities():返回用户的权限
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());

		return result;
	}

              密码前置校验如下图所示:

                      

      然后进入到retrieveUser方法中,retrieveUser和additionalAuthenticationChecks 方法

      具体的实现是DaoAuthenticationProvider 类中实现的,如下所示

@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
            // getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了
			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) {
			//。。。。。省略 。。。。。
		}
		catch (InternalAuthenticationServiceException ex) {
			//。。。。。省略 。。。。。
		}
		catch (Exception ex) {
			//。。。。。省略 。。。。。
		}
	}

//具体的密码校验逻辑
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"));
		}
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值