zoukankan      html  css  js  c++  java
  • spring security登录认证流程解析

    转 https://blog.csdn.net/qq_37142346/article/details/80032336

    1.说明

    1. 用户认证流程
    2. 认证结果如何在多个请求之间共享
    3. 获取认证用户信息、用户认证流程

    Spring Security核心就是一系列的过滤器链,当一个请求来的时候,首先要通过过滤器链的校验,校验通过之后才会访问用户各种信息。
     
    这里写图片描述

    2.当用户发送登录请求的时候,首先进入到UsernamePasswordAuthenticationFilter中进行校验。
    这里写图片描述

    2.1UsernamePasswordAuthenticationFilter部分源码

    package org.springframework.security.web.authentication;
    
    import org.springframework.security.authentication.AuthenticationServiceException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.util.Assert;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    public class UsernamePasswordAuthenticationFilter extends
            AbstractAuthenticationProcessingFilter {
        // ~ Static fields/initializers
        // =====================================================================================
    
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
        private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        private boolean postOnly = true;
    
        // ~ Constructors
        // ===================================================================================================
    
        public UsernamePasswordAuthenticationFilter() {
            super(new AntPathRequestMatcher("/login", "POST"));
        }
    
        // ~ Methods
        // ========================================================================================================
    
        public Authentication attemptAuthentication(HttpServletRequest request,
                HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }
    
            String username = obtainUsername(request);
            String password = obtainPassword(request);
    
            if (username == null) {
                username = "";
            }
    
            if (password == null) {
                password = "";
            }
    
            username = username.trim();
    
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
    
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
    }

     它继承了AbstractAuthenticationProcessingFilter

    2.2AbstractAuthenticationProcessingFilter部分源码

    package org.springframework.security.web.authentication;
    
    import java.io.IOException;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.context.ApplicationEventPublisherAware;
    import org.springframework.context.MessageSource;
    import org.springframework.context.MessageSourceAware;
    import org.springframework.context.support.MessageSourceAccessor;
    import org.springframework.security.authentication.AuthenticationDetailsSource;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.InternalAuthenticationServiceException;
    import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.SpringSecurityMessageSource;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
    import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.security.web.util.matcher.RequestMatcher;
    import org.springframework.util.Assert;
    import org.springframework.web.filter.GenericFilterBean;
    
    
    public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
            implements ApplicationEventPublisherAware, MessageSourceAware {
       
    
        protected ApplicationEventPublisher eventPublisher;
        protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
        private AuthenticationManager authenticationManager;
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
        private RememberMeServices rememberMeServices = new NullRememberMeServices();
    
        private RequestMatcher requiresAuthenticationRequestMatcher;
    
        private boolean continueChainBeforeSuccessfulAuthentication = false;
    
        private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
    
        private boolean allowSessionCreation = true;
    
        private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
    
        protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
            setFilterProcessesUrl(defaultFilterProcessesUrl);
        }
    
        
        protected AbstractAuthenticationProcessingFilter(
                RequestMatcher requiresAuthenticationRequestMatcher) {
            Assert.notNull(requiresAuthenticationRequestMatcher,
                    "requiresAuthenticationRequestMatcher cannot be null");
            this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
        }
    
        
        @Override
        public void afterPropertiesSet() {
            Assert.notNull(authenticationManager, "authenticationManager must be specified");
        }
    
       
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    
            if (!requiresAuthentication(request, response)) {  //是否需要校验
                chain.doFilter(request, response);
    
                return;
            }
    
            if (logger.isDebugEnabled()) {
                logger.debug("Request is to process authentication");
            }
    
            Authentication authResult;
    
            try {
                authResult = attemptAuthentication(request, response);  //调用attemptAuthentication方法
                if (authResult == null) {
                    // return immediately as subclass has indicated that it hasn't completed
                    // authentication
                    return;
                }
                sessionStrategy.onAuthentication(authResult, request, response);
            }
            catch (InternalAuthenticationServiceException failed) {
                logger.error(
                        "An internal error occurred while trying to authenticate the user.",
                        failed);
                unsuccessfulAuthentication(request, response, failed);
    
                return;
            }
            catch (AuthenticationException failed) {
                // Authentication failed
                unsuccessfulAuthentication(request, response, failed);
    
                return;
            }
    
            // Authentication success
            if (continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
    
            successfulAuthentication(request, response, chain, authResult);
        }
    }

    2.2.1关键方法doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      打断点发送登录请求进入源码中,我们会发现它会进入到UsernamePasswordAuthenticationFilter的attemptAuthentication这个方法中

      会获取用户的username以及password参数的信息,然后使用构造器new UsernamePasswordAuthenticationToken(username, password)封装为一个UsernamePasswordAuthenticationToken对象,在这个构造器内部会将对应的信息赋值给各自的本地变量,并且会调用父类AbstractAuthenticationToken构造器(这个父类的构造器后面会介绍到),传一个null值进去,为什么是null呢?因为刚开始并没有认证,因此用户没有任何权限,并且设置没有认证的信息(setAuthenticated(false))
    这里写图片描述

    这里写图片描述

    2.2.2在 attemptAuthentication方法末尾调用了return this.getAuthenticationManager().authenticate(authRequest);

      ProviderManager部分源码

    package org.springframework.security.authentication;
    
    import java.util.Collections;
    import java.util.List;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.MessageSource;
    import org.springframework.context.MessageSourceAware;
    import org.springframework.context.support.MessageSourceAccessor;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.CredentialsContainer;
    import org.springframework.security.core.SpringSecurityMessageSource;
    import org.springframework.util.Assert;
    
    
    public class ProviderManager implements AuthenticationManager, MessageSourceAware,
            InitializingBean {
    
    
        private static final Log logger = LogFactory.getLog(ProviderManager.class);
    
      
        private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
        private List<AuthenticationProvider> providers = Collections.emptyList();
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
        private AuthenticationManager parent;
        private boolean eraseCredentialsAfterAuthentication = true;
    
        public ProviderManager(List<AuthenticationProvider> providers) {
            this(providers, null);
        }
    
        public ProviderManager(List<AuthenticationProvider> providers,
                AuthenticationManager parent) {
            Assert.notNull(providers, "providers list cannot be null");
            this.providers = providers;
            this.parent = parent;
            checkState();
        }
    
        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();
    
            for (AuthenticationProvider provider : getProviders()) {
                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) {
                    prepareException(e, authentication);
                    // SEC-546: Avoid polling additional providers if auth failure is due to
                    // invalid account status
                    throw e;
                }
                catch (InternalAuthenticationServiceException e) {
                    prepareException(e, authentication);
                    throw e;
                }
                catch (AuthenticationException e) {
                    lastException = e;
                }
            }
    
            if (result == null && parent != null) {
                // Allow the parent to try.
                try {
                    result = parentResult = parent.authenticate(authentication);
                }
                catch (ProviderNotFoundException e) {
                    // ignore as we will throw below if no other exception occurred prior to
                    // calling parent and the parent
                    // may throw ProviderNotFound even though a provider in the child already
                    // handled the request
                }
                catch (AuthenticationException e) {
                    lastException = parentException = e;
                }
            }
    
            if (result != null) {
                if (eraseCredentialsAfterAuthentication
                        && (result instanceof CredentialsContainer)) {
                    // Authentication is complete. Remove credentials and other secret data
                    // from authentication
                    ((CredentialsContainer) result).eraseCredentials();
                }
    
                // If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
                // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
                if (parentResult == null) {
                    eventPublisher.publishAuthenticationSuccess(result);
                }
                return result;
            }
    
            // Parent was null, or didn't authenticate (or throw an exception).
    
            if (lastException == null) {
                lastException = new ProviderNotFoundException(messages.getMessage(
                        "ProviderManager.providerNotFound",
                        new Object[] { toTest.getName() },
                        "No AuthenticationProvider found for {0}"));
            }
    
            // If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
            // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
            if (parentException == null) {
                prepareException(lastException, authentication);
            }
    
            throw lastException;
        }
    
     
    }

      在ProviderManager这个实现类中,它会调用AuthenticationProvider接口的实现类获取用户的信息,用户的信息权限的验证就在该类中校验。进入ProviderManager类中调用authenticate(Authentication authentication)方法,它通过AuthenticationProvider实现类获取用户的登录的方式后会有一个for循环遍历它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果都不支持它会结束for循环,如果支持则会进入AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中调用 authenticate(Authentication authentication)方法对用户的身份进入校验。
    这里写图片描述

    2.2.3provider.authenticate(authentication)---调用AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中的 authenticate(Authentication authentication)方法对用户的身份进入校验

      AbstractUserDetailsAuthenticationProvider部分源码

    package org.springframework.security.authentication.dao;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.security.authentication.AccountExpiredException;
    import org.springframework.security.authentication.AuthenticationProvider;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.CredentialsExpiredException;
    import org.springframework.security.authentication.DisabledException;
    import org.springframework.security.authentication.LockedException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.SpringSecurityMessageSource;
    import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
    import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
    import org.springframework.security.core.userdetails.UserCache;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsChecker;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.core.userdetails.cache.NullUserCache;
    
    import org.springframework.beans.factory.InitializingBean;
    
    import org.springframework.context.MessageSource;
    import org.springframework.context.MessageSourceAware;
    import org.springframework.context.support.MessageSourceAccessor;
    
    import org.springframework.util.Assert;
    
    
    public abstract class AbstractUserDetailsAuthenticationProvider implements
            AuthenticationProvider, InitializingBean, MessageSourceAware {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
      
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
        private UserCache userCache = new NullUserCache();
        private boolean forcePrincipalAsString = false;
        protected boolean hideUserNotFoundExceptions = true;
        private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
        private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
        private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
    public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                    () -> messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.onlySupports",
                            "Only UsernamePasswordAuthenticationToken is supported"));
    
            // Determine username
            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) {
                    logger.debug("User '" + username + "' not found");
    
                    if (hideUserNotFoundExceptions) {
                        throw new BadCredentialsException(messages.getMessage(
                                "AbstractUserDetailsAuthenticationProvider.badCredentials",
                                "Bad credentials"));
                    }
                    else {
                        throw notFound;
                    }
                }
    
                Assert.notNull(user,
                        "retrieveUser returned null - a violation of the interface contract");
            }
    
            try {
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (AuthenticationException exception) {
                if (cacheWasUsed) {
                    // There was a problem, so try again after checking
                    // we're using latest data (i.e. not from the cache)
                    cacheWasUsed = false;
                    user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                    preAuthenticationChecks.check(user);
                    additionalAuthenticationChecks(user,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                else {
                    throw exception;
                }
            }
    
            postAuthenticationChecks.check(user);
    
            if (!cacheWasUsed) {
                this.userCache.putUserInCache(user);
            }
    
            Object principalToReturn = user;
    
            if (forcePrincipalAsString) {
                principalToReturn = user.getUsername();
            }
    
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }
    
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken( principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; } }

      进入抽象类AbstractUserDetailsAuthenticationProvider的内部的authenticate方法之后,先会判断user是否为空,这个user是UserDetail的对象,如果为空,表示还没有认证,就需要调用retrieveUser方法去获取用户的信息,这个方法是抽象类AbstractUserDetailsAuthenticationProvider的扩展类DaoAuthenticationProvider的一个方法。
    这里写图片描述

    2.2.4 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication)调用DaoAuthenticationProvider的retrieveUser方法

      在该扩展类的retrieveUser方法中调用UserDetailsService这个接口的实现类的loadUserByUsername方法去获取用户信息,而这里我自己编写了实现类MyUserDetail类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。
    这里写图片描述

    这里写图片描述
    在拿到用户的信息后,返回到AbstractUserDetailsAuthenticationProvider类中调用createSuccessAuthentication(principalToReturn, authentication, user)方法,在该方法中会调用三个参数的UsernamePasswordAuthenticationToken构造器,不同于前面调用两个参数的,因为这里已经验证了用户的信息和权限,因此不再是给父类构造器中传null值了,而是用户的权限集合,并且设置认证通过(setAuthenticated(true)),

    2.2.5return createSuccessAuthentication(principalToReturn, authentication, user);调用AbstractUserDetailsAuthenticationProvider的createSuccessAuthentication方法


     
    这里写图片描述
    这里写图片描述
    2.2.6 身份认证成功后,AbstractAuthenticationProcessingFilter类中调用successfulAuthentication方法

      这个方法它会调用SecurityContext,最后将认证的结果放入SecurityContextHolder中,SecurityContext类很简单,重写了equals方法和hascode方法,保证了authentication的唯一性。SecurityContextHolder类实际上对ThreadLocal的一个封装,可以在不同方法之间进行通信。

      
    这里写图片描述
    这里写图片描述
    这里写图片描述

      最后会被SecurityContextPersistenceFilter过滤器使用,这个过滤器的作用是什么呢?当一个请求来的时候,它会将session中的值传入到该线程中,当请求返回的时候,它会判断该请求线程是否有SecurityContext,如果有它会将其放入到session中,因此保证了请求结果可以在不同的请求之间共享。

  • 相关阅读:
    数据卷中的容器操作,整体来说还是非常简单的
    Docker 的另外两个话题: DockerHub 和 容器网络
    Docker——理解好镜像和容器的关系
    MapReduce 运行全过程解析
    MySQL 之 Explain 输出分析
    关于新手数组:样题:陶陶摘苹果
    VirtualBox安装Ubuntu-18.04-Server笔记
    做一个不复制粘贴的程序员[1]: 使用模板方法模式(2)- 对象更新比较器实例
    做一个不复制粘贴的程序员[1]: 使用模板方法模式(1)- 分页查询实例
    做一个不复制粘贴的程序员[0]: 概述
  • 原文地址:https://www.cnblogs.com/jthr/p/14073861.html
Copyright © 2011-2022 走看看