SpringSecurity的認證與授權(quán)

認證與授權(quán)

認證(Authentication):確定一個用戶的身份的過程政己。授權(quán)(Authorization):判斷一個用戶是否有訪問某個安全對象的權(quán)限峰锁。下面討論一下spring security中最基本的認證與授權(quán)顾瞪。

首先明確一下在認證與授權(quán)中關(guān)鍵的三個過濾器尼斧,其他過濾器不討論:

1. UsernamePasswordAuthenticationFilter:該過濾器用于攔截我們表單提交的請求(默認為/login)霜旧,進行用戶的認證過程吧冯吓。

2. ExceptionTranslationFilter:該過濾器主要用來捕獲處理spring security拋出的異常棚潦,異常主要來源于FilterSecurityInterceptor令漂。

3. FilterSecurityInterceptor:該過濾器主要用來進行授權(quán)判斷。

授權(quán)認證過程分析

訪問觸發(fā)/login

1.我們在瀏覽器中輸入http://localhost:8080/ 訪問應用丸边,因為我們的路徑被spring secuirty保護起來了叠必,我們是沒有權(quán)限訪問的,所以我們會被引導至登錄頁面進行登錄妹窖。


此路徑因為不是表單提交的路徑(/login)纬朝,該過程主要起作用的過濾器為FilterSecurityInterceptor。其部分源碼如下:

FilterSecurityInterceptor

 public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //過濾器對每個請求只處理一次
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            // filter already applied to this request and user wants us to observe
            // once-per-request handling, so don't re-do security checking
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            // first time this request being called, so perform security checking
            if (fi.getRequest() != null) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            //前處理
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                //使SecurityContextHolder中的Authentication保持原樣骄呼,因為RunAsManager會暫時改變
                //其中的Authentication
                super.finallyInvocation(token);
            }

            //調(diào)用后的處理
            super.afterInvocation(token, null);
        }
    }

真正進行權(quán)限判斷的為beforeInvocation共苛,該方法定義在FilterSecurityInterceptor的父類AbstractSecurityInterceptor中,源碼如下:

FilterSecurityInterceptor->AbstractSecurityInterceptor

 protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        final boolean debug = logger.isDebugEnabled();

        //判斷object是否為過濾器支持的類型蜓萄,在這里是FilterInvocation(里面記錄包含了請求的request,response,FilterChain)
        //這里可以把FilterInvocation看做是安全對象隅茎,因為通過它可以獲得request,通過request可以獲得請求的URI。
        //而實際的安全對象就是URI
        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
            throw new IllegalArgumentException(
                    "Security invocation attempted for object "
                            + object.getClass().getName()
                            + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                            + getSecureObjectClass());
        }

        
        //獲取安全對象所對應的ConfigAttribute嫉沽,ConfigAtrribute實際就是訪問安全所應該有的權(quán)限集辟犀。
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        //判斷安全對象是否擁有權(quán)限集,沒有的話說明所訪問的安全對象是一個公共對象绸硕,就是任何人都可以訪問的堂竟。
        if (attributes == null || attributes.isEmpty()) {
            //如果rejectPublicInvocations為true,說明不支持公共對象的訪問,此時會拋出異常玻佩。
            if (rejectPublicInvocations) {
                throw new IllegalArgumentException(
                        "Secure object invocation "
                                + object
                                + " was denied as public invocations are not allowed via this interceptor. "
                                + "This indicates a configuration error because the "
                                + "rejectPublicInvocations property is set to 'true'");
            }

            if (debug) {
                logger.debug("Public object - authentication not attempted");
            }

            publishEvent(new PublicInvocationEvent(object));

            return null; // no further work post-invocation
        }

        if (debug) {
            logger.debug("Secure object: " + object + "; Attributes: " + attributes);
        }

        //判斷SecurityCntext中是否存在Authentication,不存在則說明訪問著根本沒登錄
        //調(diào)用下面的credentialsNotFound()方法則會拋出一個AuthenticationException出嘹,
        //該異常會被ExceptionTranslationFilter捕獲,并做出處理咬崔。
        //不過默認情況下Authentication不會為null,因為AnonymouseFilter會默認注冊到
        //過濾鏈中税稼,如果用戶沒登錄的話,會將其當做匿名用戶(Anonymouse User)來對待。
        //除非你自己將AnonymouseFilter從過濾鏈中去掉娶聘。
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }

        //Autentication存在闻镶,則說明用戶已經(jīng)被認證(但是不表示已登錄,因為匿名用戶也是相當于被認證的)丸升,
        //判斷用戶是否需要再次被認證铆农,如果你配置了每次訪問必須重新驗證,那么就會再次調(diào)用AuthenticationManager
        //的authenticate方法進行驗證狡耻。
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            //判斷用戶是否有訪問被保護對象的權(quán)限墩剖。
            //ed。默認的AccessDesicisonManager的實現(xiàn)類是AffirmativeBased
            //AffirmativeBased采取投票的形式判斷用戶是否有訪問安全對象的權(quán)限
            //票就是配置的Role夷狰。AffirmativeBased采用WebExpressionVoter進行投票
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));

            throw accessDeniedException;
        }

        if (debug) {
            logger.debug("Authorization successful");
        }

        if (publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                attributes);

        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }

            // no further work post-invocation
            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                    attributes, object);
        }
        else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);

            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
    }

通過以上代碼得到以下結(jié)論
a). beforeInvocation(Object object)中的object為安全對象岭皂,類型為FilterInvocation。安全對象就是受spring security保護的對象沼头。雖然按道理來說安全對象應該是我們訪問的url爷绘,但是FilterInvocation中封裝了request,那么url也可以獲取到进倍。

b). Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object) 每個安全對象都會有對應的訪問權(quán)限集(Collection<ConfigAttribute>)土至,而且在容器啟動后所有安全對象的所有權(quán)限集就已經(jīng)被獲取到并被放在安全元數(shù)據(jù)中(SecurityMetadataSource中),通過安全元數(shù)據(jù)可以獲取到各個安全對象的權(quán)限集猾昆。因為我們每個安全對象都是登錄才可以訪問的(anyRequest().authenticated())陶因,這里我們只需要知道此時每個對象的權(quán)限集只有一個元素,并且是authenticated垂蜗。如果一個對象沒有權(quán)限集楷扬,說明它是一個公共對象,不受spring security保護贴见。

c). 當我們沒有登錄時烘苹,我們會被當做匿名用戶(Anonymouse)來看待。被當做匿名用戶對待是AnonymouseAuthenticationFilter來攔截封裝成一個Authentication對象蝇刀,當用戶被認證后就會被封裝成一個Authentication對象螟加。Authentication對象中封裝了用戶基本信息,該對象會在認證中做詳細介紹吞琐。AnonymouseAuthenticationFilter也是默認被注冊的捆探。

d). 最中進行授權(quán)判斷的是AccessDecisionManager的子類AffirmativeBased的decide方法。我在來看其decide的源碼:

AffirmativeBased-》decide

public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
            //根據(jù)用戶的authenticton和權(quán)限集得出能否訪問的結(jié)果
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            case AccessDecisionVoter.ACCESS_GRANTED:
                return;
            case AccessDecisionVoter.ACCESS_DENIED:
                deny++;

                break;

            default:
                break;
            }
        }

        if (deny > 0) {
            //如果deny>0說明沒有足夠的權(quán)限去訪問安全對象站粟,此時拋出的
            //AccessDeniedException會被ExceptionTranslationFilter捕獲處理黍图。
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
}

因為我們首次登錄,所以會拋出AccessDeniedexception奴烙。此異常會被ExceptionTranslationFilter捕獲并進行處理的助被。其部分源碼如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            // Try to extract a SpringSecurityException from the stacktrace
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                //真正處理異常的地方
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
                // Rethrow ServletExceptions and RuntimeExceptions as-is
                if (ex instanceof ServletException) {
                    throw (ServletException) ex;
                }
                else if (ex instanceof RuntimeException) {
                    throw (RuntimeException) ex;
                }

                // Wrap other Exceptions. This shouldn't actually happen
                // as we've already covered all the possibilities for doFilter
                throw new RuntimeException(ex);
            }
        }
}

private void handleSpringSecurityException(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, RuntimeException exception)
            throws IOException, ServletException {
        if (exception instanceof AuthenticationException) {
            logger.debug(
                    "Authentication exception occurred; redirecting to authentication entry point",
                    exception);
            //未被認證剖张,引導去登錄
            sendStartAuthentication(request, response, chain,
                    (AuthenticationException) exception);
        }
        else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);
                //如果為匿名用戶說明未登錄,引導去登錄
                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                                "Full authentication is required to access this resource"));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);
                //用戶已登錄揩环,但是沒有足夠權(quán)限去訪問安全對象搔弄,說明權(quán)限不足。進行
                //權(quán)限不足的提醒
                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }
}

因為我們是以匿名用戶的身份進行登錄的丰滑,所以顾犹,會被引導去登錄頁面。登錄頁面的創(chuàng)建是由默認注冊的過濾器DefaultLoginPageGeneratingFilter產(chǎn)生的褒墨。具體怎么產(chǎn)生的這里不做分析炫刷。我們只需要是誰做的就可以了。實際在使用時我們也不大可能去用默認生成的登錄頁面郁妈,因為太丑了浑玛。。噩咪。

2.在被引導至登錄頁面后顾彰,我們將輸入用戶名和密碼,提交至應用剧腻。應用會校驗用戶名和密碼拘央,校驗成功后,我們成功訪問應用书在。

此時訪問的路徑為/login,這是UsernamePasswordAuthenticationFilter將攔截請求進行認證拆又。UsernamePasswordAuthenticationFilter的doFilter方法定義在其父類AbstractAuthenticationProcessingFilter中儒旬,源碼如下:

AbstractAuthenticationProcessingFilter->doFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        //判斷請求是否需要進行驗證處理。默認對/login并且是POST請求的路徑進行攔截
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);

            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Request is to process authentication");
        }

        Authentication authResult;

        try {
            //調(diào)用UsernamePasswordAuthenticationFilter的attemptAuthentication方法進行驗證帖族,并返回
            //完整的被填充的Authentication對象
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                // authentication
                return;
            }

            //進行session固定攻擊的處理
            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) {
            // 認證失敗后的處理
            unsuccessfulAuthentication(request, response, failed);

            return;
        }

        // Authentication success
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }

        //認證成功后的處理
        successfulAuthentication(request, response, chain, authResult);
  }

實際認證發(fā)生在UsernamePasswordAuthenticationFilter的attemptAuthentication中栈源,如果認證失敗,則會調(diào)用unsuccessfulAuthentication進行失敗后的處理竖般,一般是提示用戶認證失敗甚垦,要求重新輸入用戶名和密碼,如果認證成功涣雕,那么會調(diào)用successfulAuthentication進行成功后的處理艰亮,一般是將Authentication存進SecurityContext中并跳轉(zhuǎn)至之前訪問的頁面或者默認頁面(這部分在讀者讀完本節(jié)后自行去看源碼是怎么處理的,這里不做討論挣郭,現(xiàn)在只需知道會跳到一開始我們訪問的頁面中)迄埃。下面我們來看認證即attemptAuthentication的源碼:

attemptAuthentication

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();

        //將用戶名和密碼封裝在Authentication的實現(xiàn)UsernamePasswordAuthenticationToken
        //以便于AuthentictionManager進行認證
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        //獲得AuthenticationManager進行認證
        return this.getAuthenticationManager().authenticate(authRequest);
}

spring security在進行認證時,會將用戶名和密碼封裝成一個Authentication對象兑障,在進行認證后侄非,會將Authentication的權(quán)限等信息填充完全返回蕉汪。Authentication會被存在SecurityContext中,供應用之后的授權(quán)等操作使用逞怨。此處介紹下Authentication者疤,Authentication存儲的就是訪問應用的用戶的一些信息。下面是Authentication源碼:

Authentication

public interface Authentication extends Principal, Serializable {
    //用戶的權(quán)限集合
    Collection<? extends GrantedAuthority> getAuthorities();

    //用戶登錄的憑證叠赦,一般指的就是密碼
    Object getCredentials();

    //用戶的一些額外的詳細信息宛渐,一般不用
    Object getDetails();

    //這里認為Principal就為登錄的用戶
    Object getPrincipal();

    //是否已經(jīng)被認證了
    boolean isAuthenticated();

    //設(shè)置認證的狀態(tài)
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

講解了Authentication后,我們回過頭來再看attemptAuthentication方法眯搭,該方法會調(diào)用AuthenticationManager的authenticate方法進行認證并返回一個填充完整的Authentication對象窥翩。

在這里我們又要講解一下認證的幾個核心的類,很重要鳞仙!

a). AuthenticationManager  b).ProviderManager  c).AuthenticationProvider  d).UserDetailsService  e).UserDetails

現(xiàn)在來說一下這幾個類的作用以及關(guān)聯(lián)關(guān)系寇蚊。

a). AuthenticationManager是一個接口,提供了authenticate方法用于認證棍好。

b). AuthenticationManager有一個默認的實現(xiàn)ProviderManager仗岸,其實現(xiàn)了authenticate方法。

c). ProviderManager內(nèi)部維護了一個存有AuthenticationProvider的集合借笙,ProviderManager實現(xiàn)的authenticate方法再調(diào)用這些AuthenticationProvider的authenticate方法去認證扒怖,表單提交默認用的AuthenticationProvider實現(xiàn)是DaoAuthenticationProvider。

d). AuthenticationProvider中維護了UserDetailsService业稼,我們使用內(nèi)存中的用戶盗痒,默認的實現(xiàn)是InMemoryUserDetailsManager。UserDetailsService用來查詢用戶的詳細信息低散,該詳細信息就是UserDetails俯邓。UserDetails的默認實現(xiàn)是User。查詢出來UserDetails后再對用戶輸入的密碼進行校驗熔号。校驗成功則將UserDetails中的信息填充進Authentication中返回稽鞭。校驗失敗則提醒用戶密碼錯誤。

以上說的這些接口的實現(xiàn)類是由我們在MySecurityConfig中配置時生成的引镊,即下面的代碼

configUser->AuthenticationManagerBuilder builder

@Autowired
    public void configUser(AuthenticationManagerBuilder builder) throws Exception {
        builder
            .inMemoryAuthentication()
                //創(chuàng)建用戶名為user朦蕴,密碼為password的用戶
                .withUser("user").password("password").roles("USER");
    }

這里不再討論具體是怎么生成的,記住即可弟头。因為我們實際在項目中一般都會用自定義的這些核心認證類吩抓。

下面我們來分析源碼,先來看ProviderManager的authenticate方法:

ProviderManager->authenticate

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();

        //獲取所有AuthenticationProvider亮瓷,循環(huán)進行認證
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }

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

            try {
                //對authentication進行認證
                result = provider.authenticate(authentication);

                if (result != null) {
                    //填充成完整的Authentication
                    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 = 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 = e;
            }
        }

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

            eventPublisher.publishAuthenticationSuccess(result);
            return result;
        }

        // Parent was null, or didn't authenticate (or throw an exception).

        if (lastException == null) {
            //如果所有的AuthenticationProvider進行認證完result仍然為null
            //此時表示為提供AuthenticationProvider琴拧,拋出ProviderNotFoundException異常
            lastException = new ProviderNotFoundException(messages.getMessage(
                    "ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() },
                    "No AuthenticationProvider found for {0}"));
        }

        prepareException(lastException, authentication);

        throw lastException;
}

ProviderManager用AuthenticationProvider對authentication進行認證。如果沒有提供AuthenticationProvider嘱支,那么最終將拋出ProviderNotFoundException蚓胸。

我們表單提交認證時挣饥,AuthenticationProvider默認的實現(xiàn)是DaoAuthenticationProvider,DaoAuthenticationProvider的authenticate方法定義在其父類AbstractUserDetailsAuthenticationProvider中沛膳,其源碼如下:

AbstractUserDetailsAuthenticationProvider

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 {
                //獲取UserDetails扔枫,即用戶詳細信息
                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();
        }

        //認證成功,返回裝有用戶權(quán)限等信息的authentication對象
        return createSuccessAuthentication(principalToReturn, authentication, user);
}

retrieveUser方法定義在DaoAuthenticationProvider中锹安,用來獲取UserDetails這里不再展示源碼短荐,請讀者自行去看。你會發(fā)現(xiàn)獲取獲取UserDetails正是由其中維護的UserDetailsService來完成的叹哭。獲取到UserDetails后再調(diào)用其

additionalAuthenticationChecks方法進行密碼的驗證忍宋。如果認證失敗,則拋出AuthenticationException风罩,如果認證成功則返回裝有權(quán)限等信息的Authentication對象糠排。

總結(jié)

到目前為止,我們結(jié)合我們創(chuàng)建的項目和spring security的源碼分析了web應用認證和授權(quán)的原理超升。內(nèi)容比較多入宦,現(xiàn)在理一下重點。

1.springSecurityFilterChain中各個過濾器怎么創(chuàng)建的只需了解即可室琢。不要太過關(guān)注乾闰。

2.重點記憶UsernamePasswordAuthenticationFilter,ExceptionTranslationFilter盈滴,F(xiàn)ilterSecurityInterceptor這三個過濾器的作用及源碼分析涯肩。

3.重要記憶認證中Authentication,AuthenticationManager雹熬,ProviderManager宽菜,AuthenticationProvider,UserDetailsService竿报,UserDetails這些類的作用及源碼分析。

4.重點記憶授權(quán)中FilterInvoction继谚,SecurityMetadataSource烈菌,AccessDecisionManager的作用。

5.將這些類理解的關(guān)鍵是建立起關(guān)聯(lián)花履,建立起關(guān)聯(lián)的方式就是跟著本節(jié)中的案例走下去芽世,一步步看代碼如何實現(xiàn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诡壁,一起剝皮案震驚了整個濱河市济瓢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妹卿,老刑警劉巖旺矾,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔑鹦,死亡現(xiàn)場離奇詭異,居然都是意外死亡箕宙,警方通過查閱死者的電腦和手機嚎朽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬帕,“玉大人哟忍,你說我怎么就攤上這事∠萸蓿” “怎么了锅很?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凤跑。 經(jīng)常有香客問我爆安,道長,這世上最難降的妖魔是什么饶火? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任鹏控,我火速辦了婚禮,結(jié)果婚禮上肤寝,老公的妹妹穿的比我還像新娘当辐。我一直安慰自己,他們只是感情好鲤看,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布缘揪。 她就那樣靜靜地躺著,像睡著了一般义桂。 火紅的嫁衣襯著肌膚如雪找筝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天慷吊,我揣著相機與錄音袖裕,去河邊找鬼。 笑死溉瓶,一個胖子當著我的面吹牛急鳄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堰酿,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疾宏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了触创?” 一聲冷哼從身側(cè)響起坎藐,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哼绑,沒想到半個月后岩馍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉咆,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年兼雄,在試婚紗的時候發(fā)現(xiàn)自己被綠了吟逝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赦肋,死狀恐怖块攒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佃乘,我是刑警寧澤囱井,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站趣避,受9級特大地震影響庞呕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜程帕,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一住练、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愁拭,春花似錦讲逛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惜论,卻和暖如春许赃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背馆类。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工混聊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乾巧。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓技羔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卧抗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355