Spring security oauth2认证流程分析

这几天一直在研究oauth2协议,打算按照oauth2协议做一个认证服务,使用了spring security oauth2作为工具搭建了认证服务器和资源服务器。这篇博客还不会告知如何搭建这两个服务器,我们先来简单地了解一下oauth2的认证流程,当前主要会侧重讲授权码模式。关心Spring security核心Filter创建和工作原理的,可以查看关于spring security中Filter的创建和工作原理

1.基本认证流程

认证流程示意图

2.spring security oauth2是如何实现整个认证流程的
这个问题确实有点难度,从网上查资料说是过滤器实现的,好吧,既然是过滤器实现的,那咱们开始从过滤器找线索。
首先小伙伴要知道Filter并不属于spring,而是属于tomcat,咱们可以从ApplicationFilterChain这个类入手,启动认证服务器,启动资源服务器,对资源服务器中ApplicationFilterChain的doFilter方法打上断点,开始通过客户端发送请求访问资源服务器,请求会进资源服务器,重点内容在下面这个方法里面:

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) {
            // 捕获异常,根据对应的异常发起重定向请求
            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);
            }
        }
    }

根据上面的注释,发现其实这个过滤器做的事情,就是重定向到认证服务器。
springSecurityFilterChain
因为这个对象是一个代理对象,但是我们可以找到它的被代理类,从ApplicationFilterChain中debug,咱们可以找到FilterChainProxy,代码一直在执行内部类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);
        //不存在的话,就创建一个设置进去
        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中,方便后续过滤器使用
            SecurityContextHolder.setContext(contextBeforeChainExecution);
           // 执行后续过滤器      
            chain.doFilter(holder.getRequest(), holder.getResponse());

        }
        finally {
            // 执行完后续过滤器后,取出SecurityContext
            SecurityContext contextAfterChainExecution = SecurityContextHolder
                    .getContext();
            // 清除SecurityContextHolder
            SecurityContextHolder.clearContext();
            // 将SecurityContextHolder重新设置回session中,
            // 因为在执行后续过滤器的时候,有可能发生了变化
            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 来验证,判断是否受到了跨站点攻击;处理流程简单描述如下,
每当用户登录系统某个页面的时候,通过系统后台随机生成一个 CSRF Token,通过 response 返回给客户端;客户端在发送 POST 表单提交的时候,需要将该 CSRF Token 作为隐藏字段(一般将该表单字段命名为 _csrf)提交到系统后台进行处理;系统后台会在当前的 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;
        }
        // 执行后续过滤器
        chain.doFilter(request, response);
    }

上述注释表明,这个过滤器专门处理登出请求的。
OAuth2ClientAuthenticationProcessingFilter

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

        OAuth2AccessToken accessToken;
        try {
            // 向认证服务器发送请求获取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));
            }
            // 发布成功获取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步的一个实现,通过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的用途官方解释是
用于用户登录成功后,重新恢复因为登录被打断的请求
这个解释也有几点需要说明
被打算的请求:简单点说就是出现了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() + "'");
            }
        }
        // 继续执行后面的过滤器
        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) {
            // 获取后续过滤器抛出的异常
            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,就重新执行认证
                // 如果捕获到的异常是AccessDeniedException,再进一步执行下面的判断
                // 如果当前的认证形式是Anonymous或者RememberMe,则重新执行认证 
                // 否则就是当前认证用户没有权限访问被请求资源,调用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子类会执行这里
            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);
            }
            // 执行父类beforeInvocation,类似于aop中的before
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                // 执行过滤器链
                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);
        }
    }

这个过滤器就是根据配置和权限决定请求是否正常通过,这是一个最终的决断器。
认证过程

认证流程示意图

1.当请求/client/user进资源服务器,最终请求到达AnonymousAuthenticationFilter,就拿到一个匿名Authentication,通过FilterSecurityInterceptor决断后抛出AccessDeniedException,ExceptionTranslationFilter根据异常会重定向到资源服务器登录页面

2.当请求资源服务器登录页面请求进来后,到达OAuth2ClientAuthenticationProcessingFilter,
AuthorizationCodeAccessTokenProvider

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

当没有从登录请求中拿到code和state参数时,抛出UserRedirectRequiredException,该异常会向上抛出被OAuth2ClientContextFilter捕获,OAuth2ClientContextFilter针对该异常准备好URL和请求参数,并告知客户端向认证服务器重定向。

3.当认证服务器接收到/oauth/auhorize请求的时候,最终还是给了一个AnonymousAuthentication,领到了一个AccessDeniedException,被告知要重定向到认证服务器的登录页面。

4.然后就乖乖地请求认证服务器的登录页面啦,被DefaultLoginPageGeneratingFilter截获,用户看到了登录页面。用户输入用户名密码,提交表单给认证服务器,被UsernamePasswordAuthenticationFilter截获,用户登录成功后认证服务器携带code告知客户端重定向到资源服务器登录请求。

5.客户端重定向到指定URL后,资源服务器通过OAuth2ClientAuthenticationProcessingFilter获取code。

6.资源服务器通过OAuth2ClientAuthenticationProcessingFilter获取code后,再发送请求到认证服务器获取token。认证服务器接收到/oauth/token请求,最终在TokenEndpoint生成token。

7.资源服务器拿到token后,说明用户认证通过,资源服务器告知用户重定向到第一次请求。

8.客户端根据指示重定向到第一次发起的请求,资源服务器此时已经持有用户认证信息,就能正常提供服务。

以上就是我对于spring security oauth2认证过程的一个简单分析,大部分分析内容都是针对资源服务器,认证服务器略有不同,小伙伴可以利用这个方法针对认证服务器做分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容