简体   繁体   English

如何在 Spring Boot 中使用自定义 grant_type 缓存 OAuth2RestTemplate?

[英]How to cache OAuth2RestTemplate with a cusom grant_type in Spring Boot?

I have seen similar threads but mine is different in that I am using a custom grant type .我见过类似的线程,但我的不同之处在于我使用的是自定义grant type To give you a background, when we call a micro-service from another one, we use a delegation token that has the details of the user who initiated the call.给你一个背景知识,当我们从另一个微服务调用一个微服务时,我们使用一个delegation令牌,其中包含发起调用的用户的详细信息。 So the user U1 calls S1 and S1 calls S2 so that S2 uses the U1 details for auditing and permission purposes.因此,用户U1调用S1S1调用S2 ,以便S2U1详细信息用于审计和许可目的。

Now to achieve this we have the following configuration for OAuth2RestTemplate :现在为了实现这一点,我们对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;
    }

As you can see OAuth2ClientContext has to be in request scope otherwise the previous user details will be used and token generation won't happen for second user and so on.如您所见, OAuth2ClientContext必须在request scope 中,否则将使用以前的用户详细信息,并且不会为第二个用户生成令牌,依此类推。

But this has some performance impacts.但这会对性能产生一些影响。 The effect becomes more visible when we have many concurrent users.当我们有许多并发用户时,效果变得更加明显。 So as a solution, I'm thinking to cache OAuth2ClientContext per user with a cache expiry set to a value less than the token expiry.因此,作为一种解决方案,我正在考虑为每个用户缓存OAuth2ClientContext ,并将缓存到期设置为小于令牌到期的值。 Although cache expiry isn't really an issue because each token will be validated before getting this point.尽管缓存过期并不是真正的问题,因为每个令牌都会在得到这一点之前进行验证。

Now the question is how do I achieve this and what is the best way?现在的问题是我如何实现这一目标,最好的方法是什么? From my understanding I need to change the scope from request to singleton like the default Spring bean's scope and then somehow make it to create a new instance when there is no entry in the cache? From my understanding I need to change the scope from request to singleton like the default Spring bean's scope and then somehow make it to create a new instance when there is no entry in the cache? Not sure how to do this though?不知道如何做到这一点?

OK I am answering my question as I went through some pages and tested different scenarious with this solution all of which worked well.好的,当我浏览了一些页面并使用此解决方案测试了不同的场景时,我正在回答我的问题,所有这些都运行良好。

So I've modified the method that creates a new OAuth2ClientContext to get it from a cached method:所以我修改了创建新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();
        }
    }

And this is the cached method:这是缓存的方法:

@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();
    }
}

As you can see there is an argument principal that hasn't been used in the method itself but it's important for the cache to have a key to return the right instance of the OAuth2ClientContext .正如您所看到的,有一个参数principal尚未在方法本身中使用,但对于缓存来说,拥有一个返回OAuth2ClientContext正确实例的键很重要。

I have tested this with short-time and long-time expiry for the cache, the former with a time shorter than token expiry time and the latter with a time longer than token's expiry date:我已经用缓存的短期和长期到期测试了这一点,前者的时间短于令牌到期时间,后者的时间长于令牌的到期日期:

In both cases OAuth2ClientContext is smartly requesting for new token if the token is expired or not available at all.在这两种情况下,如果令牌已过期或根本不可用, OAuth2ClientContext都会巧妙地请求新令牌。

So no issue and works like a charm.所以没有问题,就像一个魅力。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM