Spring security oauth2認證流程分析

這幾天一直在研究oauth2協(xié)議,打算按照oauth2協(xié)議做一個認證服務,使用了spring security oauth2作為工具搭建了認證服務器和資源服務器。這篇博客還不會告知如何搭建這兩個服務器,我們先來簡單地了解一下oauth2的認證流程,當前主要會側重講授權碼模式。關心Spring security核心Filter創(chuàng)建和工作原理的,可以查看關于spring security中Filter的創(chuàng)建和工作原理

1.基本認證流程

認證流程示意圖

2.spring security oauth2是如何實現(xiàn)整個認證流程的
這個問題確實有點難度,從網(wǎng)上查資料說是過濾器實現(xiàn)的,好吧,既然是過濾器實現(xiàn)的,那咱們開始從過濾器找線索。
首先小伙伴要知道Filter并不屬于spring,而是屬于tomcat,咱們可以從ApplicationFilterChain這個類入手,啟動認證服務器,啟動資源服務器,對資源服務器中ApplicationFilterChain的doFilter方法打上斷點,開始通過客戶端發(fā)送請求訪問資源服務器,請求會進資源服務器,重點內容在下面這個方法里面:

internalDoFilter(request,response);
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

上面的方法就是在調用過濾器鏈,通過debug可以觀察到有以下幾個過濾器:

過濾器鏈

仔細看一下,有兩個很明顯的過濾器,一個是OAuth2ClientContextFilter,springSecurityFilterChain,而且springSecurityFilterChain還是一個代理對象,這兩個過濾器肯定和spring security oauth2有關系。
OAuth2ClientContextFilter

public void doFilter(ServletRequest servletRequest,
            ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 記錄當前地址(currentUri)到HttpServletRequest
        request.setAttribute(CURRENT_URI, calculateCurrentUri(request));
        try {
            // 調用下一個過濾器
            chain.doFilter(servletRequest, servletResponse);
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            // 捕獲異常,根據(jù)對應的異常發(fā)起重定向請求
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
                    .getFirstThrowableOfType(
                            UserRedirectRequiredException.class, causeChain);
            if (redirect != null) {
                // 這個重定向會讓客戶端去請求認證服務器,
                //也就是認證流程示意圖中第2步重定向的操作
                //會重定向到認證服務器的/oauth/authorize
                redirectUser(redirect, request, response);
            } else {
                if (ex instanceof ServletException) {
                    throw (ServletException) ex;
                }
                if (ex instanceof RuntimeException) {
                    throw (RuntimeException) ex;
                }
                throw new NestedServletException("Unhandled exception", ex);
            }
        }
    }

根據(jù)上面的注釋,發(fā)現(xiàn)其實這個過濾器做的事情,就是重定向到認證服務器。
springSecurityFilterChain
因為這個對象是一個代理對象,但是我們可以找到它的被代理類,從ApplicationFilterChain中debug,咱們可以找到FilterChainProxy,代碼一直在執(zhí)行內部類VirtualFilterChain的doFilter方法:

@Override
        public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException {
            if (currentPosition == size) {
                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " reached end of additional filter chain; proceeding with original chain");
                }

                // Deactivate path stripping as we exit the security filter chain
                this.firewalledRequest.reset();

                originalChain.doFilter(request, response);
            }
            else {
                currentPosition++;

                Filter nextFilter = additionalFilters.get(currentPosition - 1);

                if (logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                            + " at position " + currentPosition + " of " + size
                            + " in additional filter chain; firing Filter: '"
                            + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }
        }

重點放在additionalFilters這個對象上面:


spring security filters

上圖就是spring security相關的Filters,也可以加入自定義Filter。
WebAsyncManagerIntegrationFilter

@Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 從請求中封裝一個WebAsyncManager
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        // 檢查是否存在一個SecurityContextCallableProcessingInterceptor
        SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
                .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
        //不存在的話,就創(chuàng)建一個設置進去
        if (securityProcessingInterceptor == null) {
            asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
                    new SecurityContextCallableProcessingInterceptor());
        }
        // 調用下一個過濾器
        filterChain.doFilter(request, response);
    }

這個過濾器的功能就是注冊一個SecurityContextCallableProcessingInterceptor,暫時不深究這個攔截器。
SecurityContextPersistenceFilter

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

        if (request.getAttribute(FILTER_APPLIED) != null) {
            // ensure that filter is only applied once per request
            chain.doFilter(request, response);
            return;
        }

        final boolean debug = logger.isDebugEnabled();

        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        if (forceEagerSessionCreation) {
            HttpSession session = request.getSession();

            if (debug && session.isNew()) {
                logger.debug("Eagerly created session: " + session.getId());
            }
        }
        // 將request和response封裝
        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
                response);
       // 從session中取出SecurityContext
        SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

        try {
            // 將SecurityContext放到SecurityContextHolder中,方便后續(xù)過濾器使用
            SecurityContextHolder.setContext(contextBeforeChainExecution);
           // 執(zhí)行后續(xù)過濾器      
            chain.doFilter(holder.getRequest(), holder.getResponse());

        }
        finally {
            // 執(zhí)行完后續(xù)過濾器后,取出SecurityContext
            SecurityContext contextAfterChainExecution = SecurityContextHolder
                    .getContext();
            // 清除SecurityContextHolder
            SecurityContextHolder.clearContext();
            // 將SecurityContextHolder重新設置回session中,
            // 因為在執(zhí)行后續(xù)過濾器的時候,有可能發(fā)生了變化
            repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                    holder.getResponse());
            request.removeAttribute(FILTER_APPLIED);

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

上述注釋可以說明,其實SecurityContextPersistenceFilter就是做了SecurityContext的更新操作。
HeaderWriterFilter

/**
 * Filter implementation to add headers to the current response. Can be useful to add
 * certain headers which enable browser protection. Like X-Frame-Options, X-XSS-Protection
 * and X-Content-Type-Options.
 *
 * @author Marten Deinum
 * @author Josh Cummings
 * @since 3.2
 *
 */

@Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {

        HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
                response, this.headerWriters);
        HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
                headerWriterResponse);

        try {
            filterChain.doFilter(headerWriterRequest, headerWriterResponse);
        }
        finally {
            headerWriterResponse.writeHeaders();
        }
    }

看上面注釋

Filter implementation to add headers to the current response

給當前響應添加headers,起到保護瀏覽器訪問的作用。
CsrfFilter

@Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);

        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        final boolean missingToken = csrfToken == null;
        if (missingToken) {
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);

        if (!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
        }
        if (!csrfToken.getToken().equals(actualToken)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Invalid CSRF token found for "
                        + UrlUtils.buildFullRequestUrl(request));
            }
            if (missingToken) {
                this.accessDeniedHandler.handle(request, response,
                        new MissingCsrfTokenException(actualToken));
            }
            else {
                this.accessDeniedHandler.handle(request, response,
                        new InvalidCsrfTokenException(csrfToken, actualToken));
            }
            return;
        }

        filterChain.doFilter(request, response);
    }

CsrfFilter 主要是通過驗證 CSRF Token 來驗證,判斷是否受到了跨站點攻擊;處理流程簡單描述如下,
每當用戶登錄系統(tǒng)某個頁面的時候,通過系統(tǒng)后臺隨機生成一個 CSRF Token,通過 response 返回給客戶端;客戶端在發(fā)送 POST 表單提交的時候,需要將該 CSRF Token 作為隱藏字段(一般將該表單字段命名為 _csrf)提交到系統(tǒng)后臺進行處理;系統(tǒng)后臺會在當前的 session 中一直保存該 CSRF Token,這樣,當后臺收到前端所提交的 CSRF Token 以后,將會與當前 session 中緩存的 CSRF Token 進行比對,若兩者相同,則驗證通過,若兩者不相等,則驗證失敗,拒絕訪問;Spring Security 正式通過這樣的邏輯來避免 CSRF 攻擊的
LogoutFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        // 判斷是否需要登出
        if (requiresLogout(request, response)) {
            // 獲取Authentication
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();

            if (logger.isDebugEnabled()) {
                logger.debug("Logging out user '" + auth
                        + "' and transferring to logout destination");
            }
            // 處理Authentication,一般就是直接清除
            this.handler.logout(request, response, auth);
           // 調用登出成功處理器,一般是頁面跳轉,也可自定義
            logoutSuccessHandler.onLogoutSuccess(request, response, auth);

            return;
        }
        // 執(zhí)行后續(xù)過濾器
        chain.doFilter(request, response);
    }

上述注釋表明,這個過濾器專門處理登出請求的。
OAuth2ClientAuthenticationProcessingFilter

@Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        OAuth2AccessToken accessToken;
        try {
            // 向認證服務器發(fā)送請求獲取token
            accessToken = restTemplate.getAccessToken();
        } catch (OAuth2Exception e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;          
        }
        try {
            //通過token獲取Authentication(這是一個解析token的過程)
            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
            if (authenticationDetailsSource!=null) {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
                result.setDetails(authenticationDetailsSource.buildDetails(request));
            }
            // 發(fā)布成功獲取Authentication事件
            publish(new AuthenticationSuccessEvent(result));
            return result;
        }
        catch (InvalidTokenException e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;          
        }

    }

OAuth2ClientAuthenticationProcessingFilter是專門處理認證流程第6步的一個實現(xiàn),通過code向認證服務器獲取token
RequestCacheAwareFilter

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
                (HttpServletRequest) request, (HttpServletResponse) response);

        chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
                response);
    }

這個filter的用途官方解釋是
用于用戶登錄成功后,重新恢復因為登錄被打斷的請求
這個解釋也有幾點需要說明
被打算的請求:簡單點說就是出現(xiàn)了AuthenticationException、AccessDeniedException兩類異常
重新恢復:既然能夠恢復,那肯定請求信息被保存到cache中了
SecurityContextHolderAwareRequestFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
                (HttpServletResponse) res), res);
    }

一行代碼,就是對請求做了一個包裝。
AnonymousAuthenticationFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        // 當前面的過濾器都沒有設置Authentication的時候,這里會給一個匿名的Authentication
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            SecurityContextHolder.getContext().setAuthentication(
                    createAuthentication((HttpServletRequest) req));

            if (logger.isDebugEnabled()) {
                logger.debug("Populated SecurityContextHolder with anonymous token: '"
                        + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
                        + SecurityContextHolder.getContext().getAuthentication() + "'");
            }
        }
        // 繼續(xù)執(zhí)行后面的過濾器
        chain.doFilter(req, res);
    }

這個就是用來兜底的過濾器,反正只要沒有Authentication,最終都會給一個Authentication。
SessionManagementFilter

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

        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }

        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        // 判斷當前session中是否有SPRING_SECURITY_CONTEXT屬性
        if (!securityContextRepository.containsContext(request)) {
            Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();
            // 判斷authentication是否是一個匿名的authentication
            if (authentication != null && !trustResolver.isAnonymous(authentication)) {
                // 說明用戶已經認證成功來,需要保存authentication到session中
                try {
                    sessionAuthenticationStrategy.onAuthentication(authentication,
                            request, response);
                }
                catch (SessionAuthenticationException e) {
                    // The session strategy can reject the authentication
                    logger.debug(
                            "SessionAuthenticationStrategy rejected the authentication object",
                            e);
                    // 出異常就清除,并調用失敗處理器
                    SecurityContextHolder.clearContext();
                    failureHandler.onAuthenticationFailure(request, response, e);

                    return;
                }
                //把SecurityContext設置到當前session中
                securityContextRepository.saveContext(SecurityContextHolder.getContext(),
                        request, response);
            }
            else {
                // No security context or authentication present. Check for a session
                // timeout
                if (request.getRequestedSessionId() != null
                        && !request.isRequestedSessionIdValid()) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Requested session ID "
                                + request.getRequestedSessionId() + " is invalid.");
                    }

                    if (invalidSessionStrategy != null) {
                        invalidSessionStrategy
                                .onInvalidSessionDetected(request, response);
                        return;
                    }
                }
            }
        }

        chain.doFilter(request, response);
    }

這個過濾器看名字就知道是管理session的了。
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) {
            // 獲取后續(xù)過濾器拋出的異常
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            // 看看能不能拿到AuthenticationException
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);
            // 如果不是AuthenticationException,就看看是不是AccessDeniedException
            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                if (response.isCommitted()) {
                    throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
                }
                // 真正處理AuthenticationException或AccessDeniedException
                // 如果捕獲到的異常是AuthenticationException,就重新執(zhí)行認證
                // 如果捕獲到的異常是AccessDeniedException,再進一步執(zhí)行下面的判斷
                // 如果當前的認證形式是Anonymous或者RememberMe,則重新執(zhí)行認證 
                // 否則就是當前認證用戶沒有權限訪問被請求資源,調用accessDeniedHandler.handle方法
                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);
            }
        }
    }

好吧,這個也好理解,就是專門處理異常的過濾器。
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) {
            // OncePerRequestFilter子類會執(zhí)行這里
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            // first time this request being called, so perform security checking
            if (fi.getRequest() != null && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }
            // 執(zhí)行父類beforeInvocation,類似于aop中的before
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                // 執(zhí)行過濾器鏈
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, null);
        }
    }
// 這方法就是獲取ConfigAttribute和Authentication
// 通過ConfigAttribute和Authentication判斷當前請求是否允許正常通過
// 不允許的話,就拋出對應的異常
protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        final boolean debug = logger.isDebugEnabled();

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

        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        if (attributes == null || attributes.isEmpty()) {
            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);
        }

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }

        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            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);
        }
    }

這個過濾器就是根據(jù)配置和權限決定請求是否正常通過,這是一個最終的決斷器。
認證過程

認證流程示意圖

1.當請求/client/user進資源服務器,最終請求到達AnonymousAuthenticationFilter,就拿到一個匿名Authentication,通過FilterSecurityInterceptor決斷后拋出AccessDeniedException,ExceptionTranslationFilter根據(jù)異常會重定向到資源服務器登錄頁面

2.當請求資源服務器登錄頁面請求進來后,到達OAuth2ClientAuthenticationProcessingFilter,
AuthorizationCodeAccessTokenProvider

if (request.getAuthorizationCode() == null) {
            if (request.getStateKey() == null) {
                throw getRedirectForAuthorization(resource, request);
            }

當沒有從登錄請求中拿到code和state參數(shù)時,拋出UserRedirectRequiredException,該異常會向上拋出被OAuth2ClientContextFilter捕獲,OAuth2ClientContextFilter針對該異常準備好URL和請求參數(shù),并告知客戶端向認證服務器重定向。

3.當認證服務器接收到/oauth/auhorize請求的時候,最終還是給了一個AnonymousAuthentication,領到了一個AccessDeniedException,被告知要重定向到認證服務器的登錄頁面。

4.然后就乖乖地請求認證服務器的登錄頁面啦,被DefaultLoginPageGeneratingFilter截獲,用戶看到了登錄頁面。用戶輸入用戶名密碼,提交表單給認證服務器,被UsernamePasswordAuthenticationFilter截獲,用戶登錄成功后認證服務器攜帶code告知客戶端重定向到資源服務器登錄請求。

5.客戶端重定向到指定URL后,資源服務器通過OAuth2ClientAuthenticationProcessingFilter獲取code。

6.資源服務器通過OAuth2ClientAuthenticationProcessingFilter獲取code后,再發(fā)送請求到認證服務器獲取token。認證服務器接收到/oauth/token請求,最終在TokenEndpoint生成token。

7.資源服務器拿到token后,說明用戶認證通過,資源服務器告知用戶重定向到第一次請求。

8.客戶端根據(jù)指示重定向到第一次發(fā)起的請求,資源服務器此時已經持有用戶認證信息,就能正常提供服務。

以上就是我對于spring security oauth2認證過程的一個簡單分析,大部分分析內容都是針對資源服務器,認證服務器略有不同,小伙伴可以利用這個方法針對認證服務器做分析。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容