SpringSecurity認(rèn)證原理

認(rèn)證流程原理

認(rèn)證流程

SpringSecurity是基于Filter實(shí)現(xiàn)認(rèn)證和授權(quán),底層通過FilterChainProxy代理去調(diào)用各種Filter(Filter鏈),F(xiàn)ilter通過調(diào)用AuthenticationManager完成認(rèn)證 ,通過調(diào)用AccessDecisionManager完成授權(quán),SpringSecurity中核心的過濾器鏈詳細(xì)如下:


SecurityContextPersistenceFilter

Filter的入口和出口,它是用來將SecurityContext(認(rèn)證的上下文,里面有登錄成功后的認(rèn)證授權(quán)信息)對(duì)象持久到Session的Filter,同時(shí)會(huì)把SecurityContext設(shè)置給SecurityContextHolder方便我們獲取用戶認(rèn)證授權(quán)信息

UsernamePasswordAuthenticationFilter

默認(rèn)攔截“/login”登錄請(qǐng)求,處理表單提交的登錄認(rèn)證,將請(qǐng)求中的認(rèn)證信息包括
username,password等封裝成UsernamePasswordAuthenticationToken,然后調(diào)用
AuthenticationManager的認(rèn)證方法進(jìn)行認(rèn)證

BasicAuthenticationFilter

基本認(rèn)證,支持httpBasic認(rèn)證方式的Filter

RememberAuthenticationFilter

記住我功能實(shí)現(xiàn)的Filter

AnonymousAuthenticationFilter

匿名Filter,用來處理匿名訪問的資源,如果用戶未登錄,SecurityContext中沒有Authentication,
就會(huì)創(chuàng)建匿名的Token(AnonymousAuthenticationToken),然后通過
SecurityContextHodler設(shè)置到SecurityContext中。

ExceptionTranslationFilter

用來捕獲FilterChain所有的異常,進(jìn)行處理,但是只會(huì)處理 AuthenticationException和AccessDeniedException,異常,其他的異常 會(huì)繼續(xù)拋出。

FilterSecurityInterceptor

用來做授權(quán)的Filter,通過父類(AbstractSecurityInterceptor.beforeInvocation)調(diào)用
AccessDecisionManager.decide方法對(duì)用戶進(jìn)行授權(quán)。

Security相關(guān)概念

Authentication

認(rèn)證對(duì)象,用來封裝用戶的認(rèn)證信息(賬戶狀態(tài),用戶名,密碼,權(quán)限等)所有提交給AuthenticationManager的認(rèn)證請(qǐng)求都會(huì)被封裝成一個(gè)Token的實(shí)現(xiàn),比如 最容易理解的UsernamePasswordAuthenticationToken,其中包含了用戶名和密碼

Authentication常用的實(shí)現(xiàn)類:

  • UsernamePasswordAuthenticationToken:用戶名密碼登錄的Token
  • AnonymousAuthenticationToken:針對(duì)匿名用戶的Token
  • RememberMeAuthenticationToken:記住我功能的的Token
AuthenticationManager

用戶認(rèn)證的管理類,所有的認(rèn)證請(qǐng)求(比如login)都會(huì)通過提交一個(gè)封裝了到了登錄信息的Token對(duì)象給 AuthenticationManager的authenticate()方法來實(shí)現(xiàn)認(rèn)證。AuthenticationManager會(huì) 調(diào)用AuthenticationProvider.authenticate進(jìn)行認(rèn)證。認(rèn)證成功后,返回一個(gè)包含了認(rèn) 證信息的Authentication對(duì)象。

AuthenticationProvider

認(rèn)證的具體實(shí)現(xiàn)類,一個(gè)provider是一種認(rèn)證方式的實(shí)現(xiàn),比如提交的用戶名密碼是通過和DB中查出的user記錄做比對(duì)實(shí)現(xiàn)的,那就有一個(gè)DaoProvider;如果是通過CAS請(qǐng)求單點(diǎn)登錄系統(tǒng)實(shí)現(xiàn),那就有一個(gè)CASProvider。按照Spring一貫的作風(fēng), 主流的認(rèn)證方式它都已經(jīng)提供了默認(rèn)實(shí)現(xiàn),比如DAO、LDAP、CAS、OAuth2等。 前 面講了AuthenticationManager只是一個(gè)代理接口,真正的認(rèn)證就是由 AuthenticationProvider來做的。一個(gè)AuthenticationManager可以包含多個(gè)Provider, 每個(gè)provider通過實(shí)現(xiàn)一個(gè)support方法來表示自己支持那種Token的認(rèn)證。 AuthenticationManager默認(rèn)的實(shí)現(xiàn)類是ProviderManager。

UserDetailService

用戶的認(rèn)證通過Provider來完成,而Provider會(huì)通過UserDetailService拿到數(shù)據(jù)庫(或 內(nèi)存)中的認(rèn)證信息然后和客戶端提交的認(rèn)證信息做校驗(yàn)。雖然叫Service,但是我更愿 意把它認(rèn)為是我們系統(tǒng)里經(jīng)常有的UserDao。

SecurityContext

當(dāng)用戶通過認(rèn)證之后,就會(huì)為這個(gè)用戶生成一個(gè)唯一的SecurityContext,里面包含用 戶的認(rèn)證信息Authentication。通過SecurityContext我們可以獲取到用戶的標(biāo)識(shí) Principle和授權(quán)信息GrantedAuthrity。在系統(tǒng)的任何地方只要通過 SecurityHolder.getSecruityContext()就可以獲取到SecurityContext。在Shiro中通過 SecurityUtils.getSubject()到達(dá)同樣的目的

SpringSecurity認(rèn)證流程原理

  1. 請(qǐng)求過來會(huì)被過濾器鏈中的UsernamePasswordAuthenticationFilter攔截到,請(qǐng)求中的用戶名和密碼被封裝成UsernamePasswordAuthenticationToken(Authentication的實(shí)現(xiàn)類)

  2. 過濾器將UsernamePasswordAuthenticationToken提交給認(rèn)證管理器(AuthenticationManager)進(jìn)行認(rèn)證.

  3. AuthenticationManager委托AuthenticationProvider(DaoAuthenticationProvider)進(jìn)行認(rèn)證,AuthenticationProvider通過調(diào)用UserDetailsService獲取到數(shù)據(jù)庫中存儲(chǔ)的用戶信息(UserDetails),然后調(diào)用passwordEncoder密碼編碼器對(duì)UsernamePasswordAuthenticationToken中的密碼和UserDetails中的密碼進(jìn)行比較

  4. AuthenticationProvider認(rèn)證成功后封裝Authentication并設(shè)置好用戶的信息(用戶名,密碼,權(quán)限等)返回

  5. Authentication被返回到UsernamePasswordAuthenticationFilter,通過調(diào)用SecurityContextHolder工具把Authentication封裝成SecurityContext中存儲(chǔ)起來。然后UsernamePasswordAuthenticationFilter調(diào)用AuthenticationSuccessHandler.onAuthenticationSuccess做認(rèn)證成功后續(xù)處理操作

  6. 最后SecurityContextPersistenceFilter通過SecurityContextHolder.getContext()獲取到SecurityContext對(duì)象然后調(diào)用SecurityContextRepository將SecurityContext存儲(chǔ)起來,然后調(diào)用SecurityContextHolder.clearContext方法清理SecurityContext。

  • 注意:SecurityContext是一個(gè)和當(dāng)前線程綁定的工具,在代碼的任何地方都可以通過SecurityContextHolder.getContext()獲取到登陸信息。

認(rèn)證流程源碼跟蹤

SecurityContextPersistenceFilter

這個(gè)filter是整個(gè)filter鏈的入口和出口,請(qǐng)求開始會(huì)從SecurityContextRepository中 獲取SecurityContext對(duì)象并設(shè)置給SecurityContextHolder。在請(qǐng)求完成后將SecurityContextHolder持有的SecurityContext再保存到配置好的DecurityContextRepository中,同時(shí)清除SecurityContextHolder中的SecurityContext

  • 總結(jié)一下:

SecurityContextPersistenceFilter
作用就是請(qǐng)求來的時(shí)候?qū)苏J(rèn)證授權(quán)信息的SecurityContext對(duì)象從SecurityContextRepository中取出交給SecurityContextHolder工具類,方便我們通過SecurityContextHolder獲取SecurityContext從而獲取到認(rèn)證授權(quán)信息,請(qǐng)求走的時(shí)候又把SecurityContextHolder清空,源碼如下:

public class SecurityContextPersistenceFilter extends GenericFilterBean {
  ...省略...
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  ...省略部分代碼...
  HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
            response);
  //從SecurityContextRepository獲取到SecurityContext 
    SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

    try {
     //把 securityContext設(shè)置到SecurityContextHolder,如果沒認(rèn)證通過,這個(gè)SecurtyContext就是空的
        SecurityContextHolder.setContext(contextBeforeChainExecution);
        //調(diào)用后面的filter,比如掉用usernamepasswordAuthenticationFilter實(shí)現(xiàn)認(rèn)證
        chain.doFilter(holder.getRequest(), holder.getResponse());

    }
    finally {
        //如果認(rèn)證通過了,這里可以從SecurityContextHolder.getContext();中獲取到SecurityContext
        SecurityContext contextAfterChainExecution = SecurityContextHolder
                .getContext();
        // Crucial removal of SecurityContextHolder contents - do this before anything
        // else.
         //刪除SecurityContextHolder中的SecurityContext 
        SecurityContextHolder.clearContext();
        //把SecurityContext 存儲(chǔ)到SecurityContextRepository
        repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                holder.getResponse());
        request.removeAttribute(FILTER_APPLIED);

        if (debug) {
            logger.debug("SecurityContextHolder now cleared, as request processing completed");
        }
    }
...省略...

UsernamePasswordAuthenticationFilter
它的作用是,攔截“/login”登錄請(qǐng)求,處理表單提交的登錄認(rèn)證,將請(qǐng)求中的認(rèn)證信息包括username,password等封裝成UsernamePasswordAuthenticationToken,然后調(diào)用
AuthenticationManager的認(rèn)證方法進(jìn)行認(rèn)證。

public class UsernamePasswordAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {
    // ~ Static fields/initializers
    // =====================================================================================
    //從登錄請(qǐng)求中獲取參數(shù):username,password的名字
    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;
    //默認(rèn)支持POST登錄
    private boolean postOnly = true;
    //默認(rèn)攔截/login請(qǐng)求,Post方式
    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    // ~ Methods
    // ========================================================================================================

    public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
            //判斷請(qǐng)求是否是POST
        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();
        //用戶名和密碼封裝Token
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
        //設(shè)置details屬性
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        //調(diào)用AuthenticationManager().authenticate進(jìn)行認(rèn)證,參數(shù)就是Token對(duì)象
        return this.getAuthenticationManager().authenticate(authRequest);
    }

AuthenticationManager
請(qǐng)求通過UsernamePasswordAuthenticationFilter調(diào)用AuthenticationManager,默認(rèn)走的實(shí)現(xiàn)類是ProviderManager,它會(huì)找到能支持當(dāng)前認(rèn)證的AuthenticationProvider實(shí)現(xiàn)類調(diào)用器authenticate方法執(zhí)行認(rèn)證,認(rèn)證成功后會(huì)清除密碼,然后拋出AuthenticationSuccessEvent事件

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
        ...省略...
        //這里authentication 是封裝了登錄請(qǐng)求的認(rèn)證參數(shù),
        //即:UsernamePasswordAuthenticationFilter傳入的Token對(duì)象
    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();
        //找到所有的AuthenticationProvider ,選擇合適的進(jìn)行認(rèn)證
        for (AuthenticationProvider provider : getProviders()) {
            //是否支持當(dāng)前認(rèn)證
            if (!provider.supports(toTest)) {
                continue;
            }

            if (debug) {
                logger.debug("Authentication attempt using "
                        + provider.getClass().getName());
            }

            try {
                //調(diào)用provider執(zhí)行認(rèn)證
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
                ...省略...
        }
        ...省略...
        //result就是Authentication ,使用的實(shí)現(xiàn)類依然是UsernamepasswordAuthenticationToken,
        //封裝了認(rèn)證成功后的用戶的認(rèn)證信息和授權(quán)信息
        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 than it will publish an AuthenticationSuccessEvent
        // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
        if (parentResult == null) {
            //發(fā)布事件
            eventPublisher.publishAuthenticationSuccess(result);
        }
        return result;
    }

DaoAuthenticationProvider
請(qǐng)求到達(dá)AuthenticationProvider,默認(rèn)實(shí)現(xiàn)是DaoAuthenticationProvider,它的作用是根據(jù)傳入的Token中的username調(diào)用UserDetailService加載數(shù)據(jù)庫中的認(rèn)證授權(quán)信息(UserDetails),然后使用PasswordEncoder對(duì)比用戶登錄密碼是否正確

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
        //密碼編碼器
        private PasswordEncoder passwordEncoder;
        //UserDetailsService ,根據(jù)用戶名加載UserDetails對(duì)象,從數(shù)據(jù)庫加載的認(rèn)證授權(quán)信息
        private UserDetailsService userDetailsService;
        //認(rèn)證檢查方法
        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();
        //通過passwordEncoder比較密碼,presentedPassword是用戶傳入的密碼,userDetails.getPassword()是從數(shù)據(jù)庫加載到的密碼
        //passwordEncoder編碼器不一樣比較密碼的方式也不一樣
        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"));
        }
    }

    //檢索用戶,參數(shù)為用戶名和Token對(duì)象
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            //調(diào)用UserDetailsService的loadUserByUsername方法,
            //根據(jù)用戶名檢索數(shù)據(jù)庫中的用戶,封裝成UserDetails 
            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) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }
    //創(chuàng)建認(rèn)證成功的認(rèn)證對(duì)象Authentication,使用的實(shí)現(xiàn)是UsernamepasswordAuthenticationToken,
    //封裝了認(rèn)證成功后的認(rèn)證信息和授權(quán)信息,以及賬戶的狀態(tài)等
    @Override
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
    ...省略...

這里提供了三個(gè)方法

  • additionalAuthenticationChecks:通過passwordEncoder比對(duì)密碼
  • retrieveUser:根據(jù)用戶名調(diào)用UserDetailsService加載用戶認(rèn)證授權(quán)信息
  • createSuccessAuthentication:登錄成功,創(chuàng)建認(rèn)證對(duì)象Authentication

然而你發(fā)現(xiàn) DaoAuthenticationProvider 中并沒有authenticate認(rèn)證方法,真正的認(rèn)證邏輯是通過父類AbstractUserDetailsAuthenticationProvider.authenticate方法完成的

AbstractUserDetailsAuthenticationProvider

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {
        //認(rèn)證邏輯
        public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
            //得到傳入的用戶名
            String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();
                //從緩存中得到UserDetails
            boolean cacheWasUsed = true;
            UserDetails user = this.userCache.getUserFromCache(username);
            if (user == null) {
            cacheWasUsed = false;

            try {
                //檢索用戶,底層會(huì)調(diào)用UserDetailsService加載數(shù)據(jù)庫中的UserDetails對(duì)象,保護(hù)認(rèn)證信息和授權(quán)信息
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                ...省略...
            }

            try {
                //前置檢查,主要檢查賬戶是否鎖定,賬戶是否過期等
                preAuthenticationChecks.check(user);
                //比對(duì)密碼在這個(gè)方法里面比對(duì)的
                additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (AuthenticationException exception) {
            ...省略...
            }
            //后置檢查
            postAuthenticationChecks.check(user);
    
            if (!cacheWasUsed) {
                //設(shè)置UserDetails緩存
                this.userCache.putUserInCache(user);
            }
    
            Object principalToReturn = user;
    
            if (forcePrincipalAsString) {
                principalToReturn = user.getUsername();
            }
            //認(rèn)證成功,創(chuàng)建Auhentication認(rèn)證對(duì)象
            return createSuccessAuthentication(principalToReturn, authentication, user);
}

UsernamePasswordAuthenticationFilter
認(rèn)證成功,請(qǐng)求會(huì)重新回到UsernamePasswordAuthenticationFilter,然后會(huì)通過其父類AbstractAuthenticationProcessingFilter.successfulAuthentication方法將認(rèn)證對(duì)象封裝成SecurityContext設(shè)置到SecurityContextHolder中

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

        //認(rèn)證成功,吧Authentication 設(shè)置到SecurityContextHolder
        SecurityContextHolder.getContext().setAuthentication(authResult);
        //處理記住我業(yè)務(wù)邏輯
        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
        //重定向登錄成功地址
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

然后后續(xù)請(qǐng)求又會(huì)回到SecurityContextPersistenceFilter,它就可以從SecurityContextHolder獲取到SecurityContext持久到SecurityContextRepository(默認(rèn)實(shí)現(xiàn)是HttpSessionSecurityContextRepository基于Session存儲(chǔ))

參考:https://blog.csdn.net/u014494148/article/details/108261616###

http://m.itdecent.cn/p/32fa221e03b7

https://blog.csdn.net/qq_22701869/article/details/103340878

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容