Архитектура Spring-Security-OAuth2 и анализ исходного кода

сервер Spring исходный код Безопасность

Адрес личного блога:blog.sqdyy.cn

Архитектура Spring Security OAuth2

Spring Security OAuth2 — этоOAuth2Библиотека классов, которая инкапсулируетAuthorization Server,Resource ServerиClientтриSpringВозможности, требуемые ролью приложения.Spring Security OAuthнужно сSpring Framework(Spring MVC)иSpring SecurityПредоставленные функции работают вместе при использованииSpring Security OAuthПостроитьAuthorization Server,Resource ServerиClientв случае,Spring Security OAuth2Общая схема архитектуры выглядит следующим образом:

OAuth_OAuth2Architecture

  1. владелец ресурса черезUserAgentдоступclient, где авторизация разрешает доступ к конечной точке авторизации,OAuth2RestTemplateбудет создаватьOAuth2проверенныйRESTпросить, инструктироватьUserAgentперенаправить наAuthorization ServerКонечная точка авторизации дляAuthorizationEndpoint.
  2. UserAgentдоступAuthorization Serverконечной точки авторизацииauthorizeметод, когда авторизация не зарегистрирована, конечной точке авторизации потребуется интерфейс авторизации/oauth/confirm_accessОтображается владельцу ресурса, владелец ресурса передаст авторизацию после авторизации.AuthorizationServerTokenServicesСоздайте код авторизации или токен доступа, сгенерированный токен в конечном итоге пройдетuserAgentПеренаправление передается клиенту.
  3. КлиентOAuth2RestTemplateПосле получения кода авторизации создайте запрос на доступ к серверу авторизацииTokenEndpointконечная точка токена, конечная точка токена по вызовуAuthorizationServerTokenServicesДля проверки кода авторизации, предоставленного клиентом для авторизации, и выдачи клиенту ответа на токен доступа.
  4. клиентаOAuth2RestTemplateДобавьте токен доступа, полученный от сервера авторизации, в заголовок запроса для доступа к серверу ресурсов, и сервер ресурсов передастOAuth2AuthenticationManagerперечислитьResourceServerTokenServicesПроверьте маркер доступа и проверочную информацию, связанную с маркером доступа. После успешной проверки токена доступа он возвращается клиенту для запроса соответствующего ресурса.

объяснено вышеSpring Security OAuth2Процесс выполнения трех ролей приложения, мы проанализируем архитектуру и исходный код этих трех ролей один за другим, чтобы углубить наше понимание.


Архитектура сервера авторизации

Сервер авторизации в основном предоставляет услугу аутентификации владельца ресурса.Клиент получает авторизацию от владельца ресурса через сервер авторизации, а затем получает токен, выданный сервером авторизации. В этом процессе аутентификации задействованы две важные конечные точки, одна из которых является конечной точкой авторизации.AuthorizationEndpoint, другой — конечная точка токенаTokenEndpoint. Далее будет проанализирован внутренний рабочий процесс этих двух конечных точек с помощью исходного кода.

Конечная точка авторизации

Сначала давайте посмотрим на конечную точку авторизации доступа.AuthorizationEndpointПроцесс выполнения:

OAuth_AutohrizationServerAuthArchitecture

  1. UserAgentдоступ к серверу авторизацииAuthorizationEndpoint(Конечная точка авторизации) URI:/oauth/authorize, который вызываетauthorizeметод, в основном используемый для определения того, был ли пользователь авторизован, если авторизован для выдачи нового кода авторизации, в противном случае перейти на страницу авторизации пользователя.
  2. authorizeон позвонит первымClientDetailsServiceПолучите информацию о клиенте и проверьте параметры запроса.
  3. впоследствииauthorizeЗатем метод передает параметры запроса вUserApprovalHandlerИспользуется для проверки, зарегистрирован ли клиент.scopeуполномоченный.
  4. Когда авторизация не зарегистрирована, т.е.approvedзаfalse, который покажет владельцу ресурса интерфейс для запроса авторизации/oauth/confirm_access.
  5. То же, что 4.
  6. URI конечной точки авторизации, через которую владелец ресурса снова получит доступ к серверу авторизации после подтверждения авторизации:/oauth/authorize, этот параметр запроса увеличится на единицуuser_oauth_approval, поэтому вызывается другой метод картыapproveOrDeny.
  7. approveOrDenyпозвонюuserApprovalHandler.updateAfterApprovalОпределите, следует ли обновлять в зависимости от того, авторизован ли пользовательauthorizationRequestв объектеapprovedАтрибуты.
  8. userApprovalHandlerКласс реализации по умолчанию:ApprovalStoreUserApprovalHandler, который находится внутриApprovalStoreизaddApprovalsрегистрировать авторизационную информацию.

Когда параметры запроса не переносятсяuser_oauth_approval, посетитauthorizeметод, процесс выполнения соответствует вышеперечисленным шагам 1-5, если пользователь прошел авторизацию, будет выдан новыйauthorization_code, иначе перейти на страницу авторизации пользователя:

@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
    SessionStatus sessionStatus, Principal principal) {
  // 根据请求参数封装 认证请求对象 ----> AuthorizationRequest
  // Pull out the authorization request first, using the OAuth2RequestFactory. 
  // All further logic should query off of the authorization request instead of referring back to the parameters map. 
  // The contents of the parameters map will be stored without change in the AuthorizationRequest object once it is created.
  AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
  // 获取请求参数中的response_type类型,并进行条件检验:response_type只支持token和code,即令牌和授权码
  Set<String> responseTypes = authorizationRequest.getResponseTypes();
  if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
    throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
  }
  // 请求参数必须携带客户端ID
  if (authorizationRequest.getClientId() == null) {
    throw new InvalidClientException("A client id must be provided");
  }

  try {
    // 在使用Spring Security OAuth2授权完成之前,必须先完成Spring Security对用户进行的身份验证
    if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
      throw new InsufficientAuthenticationException(
          "User must be authenticated with Spring Security before authorization can be completed.");
    }
    // 获取客户端详情
    ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
    // 获得重定向URL,它可以来自请求参数,也可以来自客户端详情,总之你需要将它存储在授权请求中
    // The resolved redirect URI is either the redirect_uri from the parameters or the one from clientDetails.
    // Either way we need to store it on the AuthorizationRequest.
    String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
    String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
    if (!StringUtils.hasText(resolvedRedirect)) {
      throw new RedirectMismatchException(
          "A redirectUri must be either supplied or preconfigured in the ClientDetails");
    }
    authorizationRequest.setRedirectUri(resolvedRedirect);
    // 根据客户端详情来校验请求参数中的scope
    // We intentionally only validate the parameters requested by the client (ignoring any data that may have been added to the request by the manager).
    oauth2RequestValidator.validateScope(authorizationRequest, client);

    // 此处检测请求的用户是否已经被授权,或者有配置默认授权的权限;若已经有accessToke存在或者被配置默认授权的权限则返回含有授权的对象
    // 用到userApprovalHandler ----> ApprovalStoreUserApprovalHandler
    // Some systems may allow for approval decisions to be remembered or approved by default. 
    // Check for such logic here, and set the approved flag on the authorization request accordingly.
    authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
        (Authentication) principal);
    // TODO: is this call necessary?
    // 如果authorizationRequest.approved为true,则将跳过Approval页面。
    boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
    authorizationRequest.setApproved(approved);
    // 已授权 直接返回对应的视图,返回的视图中包含新生成的authorization_code(固定长度的随机字符串)值
    // Validation is all done, so we can check for auto approval...
    if (authorizationRequest.isApproved()) {
      if (responseTypes.contains("token")) {
        return getImplicitGrantResponse(authorizationRequest);
      }
      if (responseTypes.contains("code")) {
        return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
            (Authentication) principal));
      }
    }

    // Place auth request into the model so that it is stored in the sessionfor approveOrDeny to use.
    // That way we make sure that auth request comes from the session,
    // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
    model.put("authorizationRequest", authorizationRequest);
    // 未授权 跳转到授权界面,让用户选择是否授权
    return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

  }
  catch (RuntimeException e) {
    sessionStatus.setComplete();
    throw e;
  }
}

Пользователь подтверждает, авторизоваться ли через страницу авторизации, и передает параметры запросаuser_oauth_approvalПолучите доступ к конечной точке авторизации, которая будет выполнятьapproveOrDenyметод, процесс выполнения соответствует шагам 6-7 выше:

@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
    SessionStatus sessionStatus, Principal principal) {
  // 在使用Spring Security OAuth2授权完成之前,必须先完成Spring Security对用户进行的身份验证
  if (!(principal instanceof Authentication)) {
    sessionStatus.setComplete();
    throw new InsufficientAuthenticationException(
        "User must be authenticated with Spring Security before authorizing an access token.");
  }
  // 获取请求参数
  AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
  if (authorizationRequest == null) {
    sessionStatus.setComplete();
    throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
  }

  try {
    // 获取请求参数中的response_type类型
    Set<String> responseTypes = authorizationRequest.getResponseTypes();
    // 设置Approval的参数
    authorizationRequest.setApprovalParameters(approvalParameters);
    // 根据用户是否授权,来决定是否更新authorizationRequest对象中的approved属性。
    authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
        (Authentication) principal);
    boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
    authorizationRequest.setApproved(approved);
    // 需要携带重定向URI
    if (authorizationRequest.getRedirectUri() == null) {
      sessionStatus.setComplete();
      throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
    }
    // 用户拒绝授权,响应错误信息到客户端的重定向URL上
    if (!authorizationRequest.isApproved()) {
      return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
          new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
          false, true, false);
    }
    // 简化模式,直接颁发访问令牌
    if (responseTypes.contains("token")) {
      return getImplicitGrantResponse(authorizationRequest).getView();
    }
    // 授权码模式,生成授权码存储并返回给客户端
    return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
  }
  finally {
    sessionStatus.setComplete();
  }
}

TokenEndpoint

Далее мы смотрим на конечную точку токенаTokenEndpointПроцесс выполнения:

OAuth_AutohrizationServerTokenArchitecture

  1. userAgentПолучив доступ к URI конечной точки токена сервера авторизации TokenEndpoint:/oauth/token, который вызываетpostAccessTokenметод, в основном используемый для созданияToken.
  2. postAccessTokenпозвонит первымClientDetailsServiceПолучите информацию о клиенте и проверьте параметры запроса.
  3. Вызовите соответствующий режим авторизации для достижения генерации классаToken.
  4. Реализованы соответствующие режимы авторизацииAbstractTokenGranterабстрактный класс, его членыAuthorizationServerTokenServicesМожет использоваться для создания, обновления, полученияtoken.
  5. AuthorizationServerTokenServicesКласс реализации по умолчанию толькоDefaultTokenServices,пропусти это的createAccessTokenспособ можно посмотретьtokenкак он был создан.
  6. реальная операцияtokenКлассTokenStore, процедура основана наTokenStoreРазличные реализации интерфейса для производства и храненияtoken.

Перечислено нижеTokenEndpointURI:/oauth/tokenАнализ исходного кода:

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
  // 在使用Spring Security OAuth2授权完成之前,必须先完成Spring Security对用户进行的身份验证
  if (!(principal instanceof Authentication)) {
    throw new InsufficientAuthenticationException(
        "There is no client authentication. Try adding an appropriate authentication filter.");
  }
  // 通过客户端Id获取客户端详情
  String clientId = getClientId(principal);
  ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
  // 根据请求参数封装 认证请求对象 ----> AuthorizationRequest
  TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

  if (clientId != null && !clientId.equals("")) {
    // Only validate the client details if a client authenticated during this
    // request.
    if (!clientId.equals(tokenRequest.getClientId())) {
      // double check to make sure that the client ID in the token request is the same as that in the
      // authenticated client
      throw new InvalidClientException("Given client ID does not match authenticated client");
    }
  }
  if (authenticatedClient != null) {
    // 根据客户端详情来校验请求参数中的scope,防止客户端越权获取更多权限
    oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
  }
  // 没有指定授权模式
  if (!StringUtils.hasText(tokenRequest.getGrantType())) {
    throw new InvalidRequestException("Missing grant type");
  }
  // 访问此端点不应该是简化模式
  if (tokenRequest.getGrantType().equals("implicit")) {
    throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
  }
  // 如果grant_type=authoraztion_code,则清空scope
  if (isAuthCodeRequest(parameters)) {
    // The scope was requested or determined during the authorization step
    if (!tokenRequest.getScope().isEmpty()) {
      logger.debug("Clearing scope of incoming token request");
      tokenRequest.setScope(Collections.<String> emptySet());
    }
  }
  // 如果grant_type=refresh_token,设置刷新令牌的scope
  if (isRefreshTokenRequest(parameters)) {
    // A refresh token has its own default scopes, so we should ignore any added by the factory here.
    tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
  }
  // 为客户端生成token
  OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  if (token == null) {
    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
  }
  return getResponse(token);
}

Самое важное в конечной точке токена — это то, как создатьtoken, различные режимы авторизации будут основаны наAbstractTokenGranterИнтерфейс реализован иначе,AbstractTokenGranterдоверитAuthorizationServerTokenServicesсоздавать, обновлять, получатьtoken.AuthorizationServerTokenServicesРеализация по умолчанию толькоDefaultTokenServices, просто извлеките егоcreateAccessTokenИсходный код метода можно увидеть:

// 生成accessToken和RefreshToken
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
  // 首先尝试获取当前存在的Token
  OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
  OAuth2RefreshToken refreshToken = null;
  // 如果现有的访问令牌accessToken不为空且没有失效,则保存现有访问令牌, 如果失效则重新存储新的访问令牌
  if (existingAccessToken != null) {
    if (existingAccessToken.isExpired()) {
      if (existingAccessToken.getRefreshToken() != null) {
        refreshToken = existingAccessToken.getRefreshToken();
        // The token store could remove the refresh token when the
        // access token is removed, but we want to
        // be sure...
        tokenStore.removeRefreshToken(refreshToken);
      }
      tokenStore.removeAccessToken(existingAccessToken);
    }
    else {
      // Re-store the access token in case the authentication has changed
      tokenStore.storeAccessToken(existingAccessToken, authentication);
      return existingAccessToken;
    }
  }
  // 如果没有刷新令牌则创建刷新令牌,如果刷新令牌过期,重新创建刷新令牌。
  // Only create a new refresh token if there wasn't an existing one associated with an expired access token.
  // Clients might be holding existing refresh tokens, so we re-use it in the case that the old access token expired.
  if (refreshToken == null) {
    refreshToken = createRefreshToken(authentication);
  }
  // But the refresh token itself might need to be re-issued if it has expired.
  else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
    ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
    if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
      refreshToken = createRefreshToken(authentication);
    }
  }
  // 生成新的访问令牌并储存,保存刷新令牌
  OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
  tokenStore.storeAccessToken(accessToken, authentication);
  // In case it was modified
  refreshToken = accessToken.getRefreshToken();
  if (refreshToken != null) {
    tokenStore.storeRefreshToken(refreshToken, authentication);
  }
  return accessToken;
}

Архитектура сервера ресурсов

Сервер ресурсов в основном используется для обработки запроса клиента на доступ к защищенному ресурсу и возврата соответствующего ответа. Сервер ресурсов проверяет допустимость маркера доступа клиента и получает информацию об аутентификации, связанную с маркером доступа. После получения информации об аутентификации убедитесь, что токен доступа разрешен.scopeВнутри поведение обработки после завершения проверки может быть реализовано аналогично обычному приложению. Ниже приведен запущенный процесс сервера ресурсов:

OAuth_ResourceServerArchitecture

  • (1) Когда клиент начинает обращаться к серверу ресурсов, он сначала проходит черезOAuth2AuthenticationProcessingFilter, роль этого перехватчика состоит в том, чтобы извлечь токен доступа из запроса, а затем извлечь аутентификационную информацию из токена.Authenticationи сохранить его в контексте.
  • (2) OAuth2AuthenticationProcessingFilterПерехватчик будет звонитьAuthenticationManager的authenticateСпособ извлечения информации аутентификации.
  • (2·) OAuth2AuthenticationProcessingFilterЕсли произойдет ошибка аутентификации, перехватчик托AuthenticationEntryPointСделайте ответ об ошибке, класс реализации по умолчаниюOAuth2AuthenticationEntryPoint.
  • (3)OAuth2AuthenticationProcessingFilterПерейти к следующему фильтру безопасности после завершения выполненияExceptionTranslationFilter.
  • (3·) ExceptionTranslationFilterФильтр используется для обработки исключений, возникающих в процессе аутентификации и авторизации системы.Если в перехватчике возникает исключение, он делегируетAccessDeniedHandlerСделайте ответ об ошибке, класс реализации по умолчаниюOAuth2AccessDeniedHandler.
  • (4) Когда проверка аутентификации/авторизации запроса прошла успешно, вернуть соответствующий ресурс, который должен запросить клиент.

Сервер ресурсов, о котором нам нужно позаботиться, — это то, как он проверяет, что токен доступа клиента действителен, поэтому мы начнем сOAuth2AuthenticationProcessingFilterНачиная с исходного кода, роль этого перехватчика состоит в том, чтобы извлечь токен доступа из запроса, а затем извлечь аутентификационную информацию из токена.Authenticationи поместите это в контекст:

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

    try {
        // 从请求中提取token,然后再提取token中的认证信息Authorization
        Authentication authentication = this.tokenExtractor.extract(request);
        if (authentication == null) {
            if (this.stateless && this.isAuthenticated()) {
                if (debug) {
                    logger.debug("Clearing security context.");
                }

                SecurityContextHolder.clearContext();
            }

            if (debug) {
                logger.debug("No token in request, will continue chain.");
            }
        } else {
            request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
            if (authentication instanceof AbstractAuthenticationToken) {
                AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
                needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
            }
            //获取token携带的认证信息Authentication并进行验证,然后存到spring security的上下文,以供后续使用 
            Authentication authResult = this.authenticationManager.authenticate(authentication);
            if (debug) {
                logger.debug("Authentication success: " + authResult);
            }
            this.eventPublisher.publishAuthenticationSuccess(authResult);
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
    } catch (OAuth2Exception var9) {
        SecurityContextHolder.clearContext();
        if (debug) {
            logger.debug("Authentication request failed: " + var9);
        }
        this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
        this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
        return;
    }

    chain.doFilter(request, response);
}

В приведенном выше коде упоминаетсяOauth2AuthenticationManagerполучитеtokenПереносимая информация аутентификации аутентифицируется.Из исходного кода мы можем знать, что он в основном выполняет 3 шага:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

  if (authentication == null) {
    throw new InvalidTokenException("Invalid token (token not found)");
  }
  String token = (String) authentication.getPrincipal();
  // 1.通过token获取OAuuth2Authentication对象
  OAuth2Authentication auth = tokenServices.loadAuthentication(token);
  if (auth == null) {
    throw new InvalidTokenException("Invalid token: " + token);
  }
  // 2.验证资源服务的ID是否正确
  Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
  if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
    throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
  }
  // 3.验证客户端的访问范围(scope)
  checkClientDetails(auth);

  if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
    // Guard against a cached copy of the same details
    if (!details.equals(auth.getDetails())) {
      // Preserve the authentication details from the one loaded by token services
      details.setDecodedDetails(auth.getDetails());
    }
  }
  auth.setDetails(authentication.getDetails());
  auth.setAuthenticated(true);
  return auth;

}

После проверки, послеExceptionTranslationFilterфильтр для доступа к ресурсу.


Клиентская архитектура

Spring security OAuth2клиентские элементы управленияOAuth 2.0Права доступа к защищенным ресурсам других серверов. Конфигурация включает в себя установление соединения между связанным защищенным ресурсом и пользователем, имеющим разрешение на доступ к ресурсу. В клиенте также необходимо реализовать функцию хранения кода авторизации пользователя и токена доступа.

OAuth_ClientArchitecture.png

Структура клиентского кода не отличается особой сложностью, вот описание схемы архитектуры, если интересно, можете изучить исходный код по описанному ниже процессу:

  1. во-первыхUserAgentзвонящий клиентController, который пройдет раньшеOAuth2ClientContextFilterфильтр, который в основном используется для захвата того, что может произойти на шаге 5UserRedirectRequiredException, чтобы перенаправить на сервер авторизации для повторной авторизации.
  2. Необходимо внедрить код, относящийся к уровню обслуживания клиента.RestOperations->OAuth2RestOperationsКласс реализации интерфейсаOAuth2RestTemplate. В основном он обеспечивает доступ к серверу авторизации или серверу ресурсов.RestAPI.
  3. OAuth2RestTemplateчленOAuth2ClientContextКласс реализации интерфейсаDefaultOAuth2ClientContext. Он проверит, действителен ли токен доступа, и, если он действителен, выполнит шаг 6, чтобы получить доступ к серверу ресурсов.
  4. Если токен доступа не существует или срок его действия истек, вызовитеAccessTokenProviderдля получения токена доступа.
  5. AccessTokenProviderПолучите токен доступа в соответствии с определенными сведениями о ресурсе и типом авторизации, если нет, бросьтеUserRedirectRequiredException.
  6. Укажите токен доступа, полученный в 3 или 5, для доступа к серверу ресурсов. Если во время доступа возникает исключение срока действия маркера, инициализируйте сохраненный маркер доступа, а затем перейдите к шагу 4.

Схема архитектуры и часть содержания в тексте взяты изРуководство по разработке TERASOLUNA Server Framework (5.x),Если воспроизводится, укажите источник.