Spring Security(五):认证流程源码解析

对于前面几篇的学习应该能学到很多的功能点,但也只是知道如何实现而已,关于底层完全是个黑盒状态,而且对于这些知识点都很碎片化,这一篇就来解析一下认证流程的底层源码,将整个认证流程串联起来。

知识回顾

认证流程入门实践:

功能总结

功能 实现
处理用户信息的获取逻辑 实现UserDetailsService接口
处理用户校验逻辑 实现UserDetails接口
密码的加解密 实现PasswordEncoder接口
自定义登录成功处理 实现AuthenticationSuccessHandler接口
自定义登陆失败处理 实现AuthenticationFailHandler接口

认证处理流程源码

基本原理

  • Spring Security基本原理图
    处理流程图

认证流程

  • 认证流程图
    认证流程图
  • 第一步:首先进入 UsernamePasswordAuthenticationFilter,得到前端传过来的用户名和密码,然后拿用户名密码构建了UsernamePasswordAuthenticationToken对象(未授权),其类是Authentication的一个实现,该接口封装了认证信息,最后AuthenticationManager调用了authenticate方法,即图中的第二步,其作用是用来管理第三步的AuthenticationProvider
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    
    
        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 authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  • 第二步:进入authenticate方法所在的类ProviderManager,其实现了AuthenticationManager接口,方法里边有一个for循环,拿到所有的AuthenticationProvider,为什么是循环呢,是因为不同的认证逻辑是不一样的,AuthenticationManager负责把所有认证请求收集起来,然后每个去循环判断是否支持方法传进来的登录方式,如果支持的话就会进行一个认证处理,即调用provider的authenticate方法。
	while(var6.hasNext()) {
    
    
        AuthenticationProvider provider = (AuthenticationProvider)var6.next();
        if (provider.supports(toTest)) {
    
    
            if (debug) {
    
    
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
    
    
                result = provider.authenticate(authentication);
                if (result != null) {
    
    
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var11) {
    
    
                this.prepareException(var11, authentication);
                throw var11;
            } catch (InternalAuthenticationServiceException var12) {
    
    
                this.prepareException(var12, authentication);
                throw var12;
            } catch (AuthenticationException var13) {
    
    
                lastException = var13;
            }
        }
    }
  • 第三步:实际上调用的是AbstractUserDetailsAuthenticationProvider这个抽象类的方法,该方法里边调用了一个retrieveUser的方法,他被类DaoAuthenticationProvider进行了实现,这个方法里面有一句this.getUserDetailsService().loadUserByUsername(username);,实际上就是调用了我们UserDetailsService这个接口的实现来获取UserDetails,这就和我们自定义认证逻辑那一块衔接上了。
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    
    
        UserDetails loadedUser;
        try {
    
    
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        } catch (UsernameNotFoundException var6) {
    
    
            if (authentication.getCredentials() != null) {
    
    
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
            }

            throw var6;
        } catch (Exception var7) {
    
    
            throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
        }

        if (loadedUser == null) {
    
    
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
    
    
            return loadedUser;
        }
    }
  • 第四步:上一步调用了我们自己的loadUserByUsername方法,并返回到第三步,得到了UserDetails对象;继续往下走,执行this.preAuthenticationChecks.check(user);进行一个预检查,里面的逻辑就是判断UserDetails的一些bool属性,包括账户是否锁定、是否可用、是否过期,如果满足某一个的话就会抛出对应的异常。预检查之后会调用additionalAuthenticationChecks方法进行附加的检查,附加检查里面主要检查密码是否匹配。预检查之后最后还会进行this.postAuthenticationChecks.check(user);后检查,这个检查就是判断UserDetails最后一个bool属性,检查认证信息是否过期。
	try {
    
    
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
    
    
            if (!cacheWasUsed) {
    
    
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
    
    
            this.userCache.putUserInCache(user);
        }

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

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
  • 第五步:在所有检查之后,会根据authenticationuser创建一个Success的Authentication,里边重新创建了一个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;
    }
  • 第六步:回到AbstractAuthenticationProcessingFilterdoFilter方法,在调用了attemptAuthentication方法走了以上流程拿到认证成功的Authentication之后,会调用successfulAuthentication方法,里面最后一步this.successHandler.onAuthenticationSuccess(request, response, 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);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
    
    
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • 第七步:只要以上某一步抛出了异常,异常会被捕获,捕获后会调用this.unsuccessfulAuthentication(request, response, var8);,里面的处理逻辑this.failureHandler.onAuthenticationFailure(request, response, failed);就是在调用自己写的登录失败处理器了。
	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;
    }

认证结果如何在多个请求之间共享

思考?

  • 首先在多个请求之间共享肯定是放到session里的,Spring Security是什么时候把什么东西放到了session里,什么时候从session里读出来的?

认证信息存取

  • 认证流程后续图
    后续图
  • 实际上在上面认证成功之后的successfulAuthentication方法里,这一行SecurityContextHolder.getContext().setAuthentication(authResult);就是把认证成功的Authentication保存到SecurityContext接口的实现类SecurityContextImpl中,再保存到SecurityContextHolder
  • 那么谁来用SecurityContextHolder呢?其实就是最后一步的SecurityContextPersistenceFilter过滤器,它在我们之前提到的过滤器连的最前面;它的作用有两个,当请求进来,先进这个过滤器,检查session是否有SecurityContext,如果有就把它从session里拿出来放到线程里;当过滤器链走完,请求出去时,又经过这个过滤器,这时会检查线程是否有SecurityContext,有的话就拿出来放到session里边。
  • 这样的话,不同请求就可以在session中拿到相同的认证信息,拿到以后放到线程里边,因为整个请求都是在一个线程里边完成。所以在线程的任何位置随时可以使用SecurityContextHolder.getContext()来获取到认证信息。
  • 基本原理完整图
    过滤器链图

获取认证用户信息

方式一

	@GetMapping("/me")
	public Object getCurrentUser() {
    
    
		return SecurityContextHolder.getContext().getAuthentication();
	}

方式二

	@GetMapping("/me")
	public Object getCurrentUser(Authentication authentication) {
    
    
		return authentication;
	}

只获取UserDetails

	@GetMapping("/me")
	public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
    
    
		return userDetails;
	}

猜你喜欢

转载自blog.csdn.net/qq_36221788/article/details/105871472