Spring Security Oauth2 执行流程剖析

1.工作原理及过程

OAuth 2.0的运行流程如下图

 本篇案列主要基于授权码模式(authorization code)一下是授权码模式的运行流程如下图

2.时序图展示

3.核心源码解析

基于spring security  Oauth2主要就三个类:

1.AbstractAuthenticationProcessingFilter  主要是用来对用户的访问请求进行拦截认证的入口

2.AuthorizationEndpoint  主要是匹配授权模式(1.授权码模式 2:隐匿模式 3:客户端模式 4密码模式)返回不同的ModelAndView(重定向到login路由)对象 并且携带凭证、权限信息

3.TokenPoint 校验凭证信息 并且颁发Token

3.1.1 AbstractAuthenticationProcessingFilter 

AbstractAuthenticationProcessingFilter.doFilter()方法

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
       //判断该请求是否需要进行权限校验 内部调用requiresAuthenticationRequestMatcher.matches
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
                 //对该请求的用户信息进行认证返回的是一个Authentication对象 
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
                
            //认证成功 封装request请求 并将authentication存储到上下文对象中最后进行路由转发
            this.successfulAuthentication(request, response, chain, authResult);
        }
    }

这里重点提一下requiresAuthentication方法内部调用的是requiresAuthenticationRequestMatcher方法

   protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        return this.requiresAuthenticationRequestMatcher.matches(request);
    }

我们在进行spring security oauth2的配置的时候会有这么一个类WebSecurityConfigurerAdapter 的自定义实现的子类里面的antMatchers方法这里面   .antMatchers("/", "/login**")被装载进requiresAuthenticationRequestMatcher。

@EnableOAuth2Sso
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/", "/login**")
            .permitAll()
            .anyRequest()
            .authenticated();
    }

}

3.1.2 AuthorizationEndpoint 

AuthorizationEndpoint.auhorize方法

 @RequestMapping({"/oauth/authorize"})
    public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
   //根据response_type、client_id、state、redirect_uri生成封装的authorizationRequest
        AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
  //获取返回类型这里默认值是code    
        Set<String> responseTypes = authorizationRequest.getResponseTypes();
        if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
            throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
        } else if (authorizationRequest.getClientId() == null) {
            throw new InvalidClientException("A client id must be provided");
        } else {
            try {
                if (principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
//根据client_id获取ClinetDetails对象 这里我们默认是在内存中进行配置的,所以调用的是inMemoryClientDetailsService
                    ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
//获取重定向的uri 这里默认是login 路由
                    String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
                    String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
                    if (!StringUtils.hasText(resolvedRedirect)) {
                        throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
                    } else {
//设置重定向路由
                        authorizationRequest.setRedirectUri(resolvedRedirect);
//验证scope范围
                        this.oauth2RequestValidator.validateScope(authorizationRequest, client);
                        authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
//用户是否许可、一种是默认方式为true 一种是手动授权方式 
                        boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
                        authorizationRequest.setApproved(approved);
                        if (authorizationRequest.isApproved()) {
                            if (responseTypes.contains("token")) {
                                return this.getImplicitGrantResponse(authorizationRequest);
                            }

                            if (responseTypes.contains("code")) {
//返回视图重定向到登录页面并且携带用户信息、client等信息
                                return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
                            }
                        }

                        model.put("authorizationRequest", authorizationRequest);
                        model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", this.unmodifiableMap(authorizationRequest));
                        return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
                    }
                } else {
                    throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
                }
            } catch (RuntimeException var11) {
                sessionStatus.setComplete();
                throw var11;
            }
        }
    }

3.1.2 TokenPoint

public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        if (!(principal instanceof Authentication)) {
            throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
        } else {
//获取clientId信息
            String clientId = this.getClientId(principal);
//获取ClientDetails信息
            ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);

            TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
            if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
                throw new InvalidClientException("Given client ID does not match authenticated client");
            } else {
                if (authenticatedClient != null) {
                    this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
                }

                if (!StringUtils.hasText(tokenRequest.getGrantType())) {
                    throw new InvalidRequestException("Missing grant type");
                } else if (tokenRequest.getGrantType().equals("implicit")) {
                    throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
                } else {
                    if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
                        this.logger.debug("Clearing scope of incoming token request");
                        tokenRequest.setScope(Collections.emptySet());
                    }

                    if (this.isRefreshTokenRequest(parameters)) {
                        tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
                    }
//调用DefaultTokenService 的createAccessToken方法
                    OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
                    if (token == null) {
                        throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
                    } else {
                        return this.getResponse(token);
                    }
                }
            }
        }
    }

生成token

 private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
        int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
        if (validitySeconds > 0) {
            token.setExpiration(new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L));
        }

        token.setRefreshToken(refreshToken);
        token.setScope(authentication.getOAuth2Request().getScope());
        return (OAuth2AccessToken)(this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
    }

总结:

访问受限资源-------filter拦截-------验证是否认证没有---------定向到登录页面-------------filter拦截---------------进行用户身份校验-通过----------生成authenticaiotn信息并且定向到/oauth/authorize----------对authentication进行一些校验、授权、封装重定向到登录页面--------filter拦截-重现走一遍认证逻辑--------颁发token

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值