Как кэшировать OAuth2RestTemplate cusom grant_type в Spring Boot? - PullRequest
0 голосов
/ 27 апреля 2020

Я видел похожие темы, но моя отличается тем, что я использую пользовательские grant type. Чтобы дать вам представление, когда мы вызываем микросервис из другого, мы используем токен delegation, в котором есть данные о пользователе, который инициировал вызов. Поэтому пользователь U1 звонит S1 и S1 , звонит S2 , поэтому S2 использует U1 детали для целей аудита и разрешений.

Теперь, чтобы достичь этого, у нас есть следующая конфигурация для OAuth2RestTemplate:

    @Bean(name = "delegationResource")
    @Autowired
    public OAuth2ProtectedResourceDetails delegationResource(OAuth2ClientAuthenticationSettings settings) {
        OAuth2AuthenticationSettings authSettings = authenticationSettings != null ? authenticationSettings : new OAuth2AuthenticationSettings();
        StringBuilder url = new StringBuilder();
        url.append(settings.getAuthorisationUrl() != null ? settings.getAuthorisationUrl() : authSettings.getUrl());
        url.append(settings.getAccessTokenPath());

        DelegationResourceDetails details = new DelegationResourceDetails(authenticationFacade);
        details.setClientId(settings.getClientId());
        details.setClientSecret(settings.getClientSecret());
        details.setAccessTokenUri(url.toString());
        details.setGrantType("custom");
        if(settings.getScopes() != null) {
            details.setScope(Arrays.asList(settings.getScopes()));
        }
        return details;
    }

    @Bean(name = "requestScopeClientContext")
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) //Without request-scope, RestTemplate is not thread-safe
    public OAuth2ClientContext requestScopeClientContext() {
        //This is used for delegation requests and needs to be scoped as request otherwise the first token will be used for all other subsequent calls regardless of what user is initiating it
        return new DefaultOAuth2ClientContext();
    }

    @Autowired
    CorrelationIDInterceptor correlationIDInterceptor;

    @Bean(name = "delegationOauth2RestTemplate")
    //if access to a third party resource is required, a new bean should be created with a @Qualifier
    @Autowired
    public OAuth2RestTemplate clientCredentialDelegationOauth2RestTemplate(@Qualifier("delegationResource") OAuth2ProtectedResourceDetails delegationResource, @Qualifier("requestScopeClientContext")  OAuth2ClientContext sessionScopeClientContext) {
        /*
        This is used to send requests from a micro-service to another on behalf of the user who initiated the original request
        so this has to have a thread-safe client-context
         */
        ArrayList<ClientHttpRequestInterceptor> clientHttpRequestInterceptors = new ArrayList<>();
        clientHttpRequestInterceptors.add(correlationIDInterceptor);
        DelegationOAuth2RestTemplate delegationOAuth2RestTemplate = new DelegationOAuth2RestTemplate(delegationResource, sessionScopeClientContext);
        delegationOAuth2RestTemplate.setInterceptors(clientHttpRequestInterceptors);
        return delegationOAuth2RestTemplate;
    }

Как видите, OAuth2ClientContext должен быть в request область действия, в противном случае будут использоваться предыдущие данные пользователя, и генерация токенов для второго пользователя не произойдет и т. Д.

Но это оказывает некоторое влияние на производительность. Эффект становится более заметным, когда у нас много одновременно работающих пользователей. Поэтому в качестве решения я собираюсь кэшировать OAuth2ClientContext на пользователя с установленным значением срока действия кэша, которое меньше значения срока действия токена. Хотя истечение срока действия кэша на самом деле не является проблемой, потому что каждый токен будет проверен до получения этой точки.

Теперь вопрос в том, как мне этого добиться и как лучше? Насколько я понимаю, мне нужно изменить область действия с request на singleton, как область действия стандартного бина Spring, а затем каким-то образом создать его, чтобы создать новый экземпляр, когда в кэше нет записи? Не знаете, как это сделать?

1 Ответ

0 голосов
/ 29 апреля 2020

ОК. Я отвечаю на свой вопрос, просматривая несколько страниц и тестируя различные сценарии с помощью этого решения, которое все работало хорошо.

Поэтому я изменил метод, который создает новый OAuth2ClientContext, чтобы получить это из кэшированного метода:

    @Bean(name = "requestScopeClientContext")
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) //Without request-scope, RestTemplate is not thread-safe
    public OAuth2ClientContext requestScopeClientContext() {
        if(!authenticationSettings.getDisableCachedToken()) {
            String username = authenticationFacade.getPrincipal().getUsername();
            DefaultOAuth2ClientContext occ = cachedService.getOAuth2ClientContextByUser(username);
            logger.debug("DefaultOAuth2ClientContext is fetched with id {} for user {}", occ.hashCode(), username);
            return occ;
        } else {
            logger.debug("DefaultOAuth2ClientContext has not been cached!");
            return new DefaultOAuth2ClientContext();
        }
    }

И это кэшированный метод:

@Service
public class CachedService {

    /**
     * The <code>principal</code> argument is used as an entry for the underlying cache so that
     * Spring doesn't call to issue new token if there is already available for that user.
     * The option <code>sync</code> is set to true for concurrent requests, if not true all the calls to this method will initiate cache calculation
     * before the cache being updated even if they are all the same user, setting true ensures the first call does the computation and the
     * rest will benefit from the cache.
     * @param principal
     * @return
     */
    @Cacheable(value = "delegation", sync = true, key = "#principal", cacheManager = "caffeine")
    public DefaultOAuth2ClientContext getOAuth2ClientContextByUser(String principal) {
        System.out.println("CALLED");
        //This is used for delegation requests and needs to be scoped as request otherwise the first token will be used for all other subsequent calls regardless of what user is initiating it
        return new DefaultOAuth2ClientContext();
    }
}

Как вы можете видеть, есть аргумент principal, который не использовался в сам метод, но для кеша важно иметь ключ для возврата правильного экземпляра OAuth2ClientContext.

Я проверил это с кратковременным и долгосрочным истечением срока действия кеша, первый с время короче, чем время истечения токена, а время последнего больше, чем дата истечения токена:

В обоих случаях OAuth2ClientContext разумно запрашивает новый токен, если токен истек или вообще не доступен.

Так что нет проблем и работает как шарм.

...