如何在Spring Boot中使用cusom grant_type缓存OAuth2RestTemplate?

问题描述 投票:0回答:1

我见过类似的线程,但是我的不同之处在于我使用的是自定义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 bean的范围一样,然后在缓存中没有任何条目时以某种方式使其创建一个新实例?不确定如何执行此操作吗?

spring spring-boot spring-oauth2 requestscope
1个回答
0
投票

确定,当我浏览某些页面并使用该解决方案测试了不同的方案时,我都在回答我的问题,所有这些都很好用。

因此,我修改了创建新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都会聪明地请求新令牌。

所以没问题,就像一个咒语。

© www.soinside.com 2019 - 2024. All rights reserved.