当前位置:   article > 正文

Spring Security源码分析_sprigsecurity6.2源码分析

sprigsecurity6.2源码分析

Spring Security 是一个和Spring无缝衔接的重量级安全权限框架,相信各位小码农们对于Security 是比较熟悉的,重量级,上手快,和Spring无缝衔接,自从SpringBoot框架横空出世后,Security的使用变得更加容易了。本文旨在从实际应用角度出发,阅读 Spring Security 源码,分析其实现原理。

  1. 为了方便后面理解,这里先放大致流程时序图,先来看看认证的逻辑是怎样的,如下图:
    (1)UserPasswordAuthentication登录时的认证逻辑
    在这里插入图片描述
    相信各位都知道SpringSecurity认证流程是有一系列过滤器组成的,接下来根据时序图逐一分析
  2. org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
    (1)filterProcessesUr 要被拦截的请求,不填写默认为 /** 拦截所有请求
    在这里插入图片描述
    (2)过滤器方法doFilter开始认证
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        //1. 判断请求是否需要认证,如果不需要认证则跳过,否则继续向下执行
        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 {
            	//2.认证主要是这个方法
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }
				//3.认证信息会话
                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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

(3)密码模式的认证器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

 //如果不是POST请求则抛出异常
 if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
        	//调用request.getParameter()获取用户名和密码
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            //token类型,比较重要有三个字段principal和credentials,authorities,principal默认为用户名,credentials默认为密码,此时认证信息默认为fasle,各位可以点进去看一下构造器
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            //一些url请求信息,跳过
            this.setDetails(request, authRequest);
            //这个时候认证流程的主角登场了AuthenticationManager,后面会详细讲一下AuthenticationManager初始化构建过程,由实现类ProviderManager管理认证流程
            return this.getAuthenticationManager().authenticate(authRequest);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. AuthenticationManager接口实现类ProviderManager,ProviderManager管理各种认证器,也是以后自定义认证信息的重要一点。
    (1)存在的一个集合属性,AuthenticationManager初始化时,默认是DaoAuthenticationProvider,可以自定义,但是如果自定义多个AuthenticationProvider,Security不会初始化bean,这个很重要,被坑惨了,后面贴源码
 List<AuthenticationProvider> providers;
  • 1
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   		//1. token类型
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        
        //AuthenticationProvider认证器集合
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            //这一步supports就是使用token类型找到匹配的AuthenticationProvider认证器
            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;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var9) {
            } catch (AuthenticationException var10) {
                lastException = var10;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            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}"));
            }

            this.prepareException((AuthenticationException)lastException, authentication);
            throw lastException;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

(2)可以实现AuthenticationProvider自定义认证器,但是要重写supports方法,该方法根据token类型选择认证器。
默认就是前面提到的UsernamePasswordAuthenticationToken,记得自定义token,可以选择继承AbstractAuthenticationToken

 public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
  • 1
  • 2
  • 3
  1. 主要用来获取用户信息,校验密码等流程。并且自定义多个UserDetailsServiceSecurity也不会初始化bean。刚学Security时,都知道继承UserDetailsService从数据库查用户信息,现在来看看为什么。详细流程写在后面。org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
		//从token中获取用户名
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
            	//提到的UserDetailService使用的地方,用来获取用户信息,自定义逻辑
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

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

        try {
        	//判断用户是否可用,锁定,过期
            this.preAuthenticationChecks.check(user);
            //校验密码,也是平时很疑惑Security到底怎么做到自动校验用户名密码的位置
            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();
        }
		//最后校验成功返回UsernamePasswordAuthenticationToken
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

(3)调用UserDetailService获取用户信息,可以自定义逻辑
org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
在这里插入图片描述
(4)前置校验,校验用户是否可用,锁定,过期org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks#check
在这里插入图片描述(5)校验密码,也是平时很疑惑Security到底怎么做到自动校验用户名密码的位置
org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		//盐值
        Object salt = null;
        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
        	//从token获取前端传的密码
            String presentedPassword = authentication.getCredentials().toString();
            //开始校验密码,PasswordEncoder自定义设置加解密,userDetails则是UserDetailService.loadUserByUsername获取用户数据。最后两个密码进行校验
            if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(6)后置校验,用户凭证是否过期
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks#check
在这里插入图片描述
(7)最后校验成功返回UsernamePasswordAuthenticationToken
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication
在这里插入图片描述
3. 最后了,认证流程已经结束了,还有我们使用Security时经常要配置SuccessHandler或者FailureHandler,这也是最后一步所要看的东西。重新回到第一步的AbstractAuthenticationProcessingFilter。前面已经看到,在这个首要的过滤器中,我们使用
attemptAuthentication进行认证。再把代码复制一遍过来,看一下成功和失败的逻辑分别是什么

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            -------------------------代码已省略
            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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

(2)认证失败逻辑,可以看到会把SecurityContextHolder当前线程信息清除(即是 ThreadLocal),最后调用AuthenticationFailureHandler,可以自定义实现FailureHandler
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#unsuccessfulAuthentication
在这里插入图片描述(3)认证成功逻辑,首先在当前线程保存认证信息,其次发布事件通知,可以自定义。最后调用SuccessHandler,自定义实现业务逻辑
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#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);
        }
		//认证信息保存在SecurityContextHolder中,当前线程
        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        //发布事件通知,可以自定义实现逻辑,和SuccessHandler一样的效果
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        //执行SuccessHandler,可以自定义实现该方法业务逻辑
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/708537
推荐阅读
相关标签
  

闽ICP备14008679号