【问题标题】:How to cache OAuth2RestTemplate with a cusom grant_type in Spring Boot?如何在 Spring Boot 中使用自定义授权类型缓存 OAuth2RestTemplate?
【发布时间】:2020-04-27 06:46:56
【问题描述】:

我见过类似的线程,但我的不同之处在于我使用的是自定义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】:

    好的,我正在回答我的问题,因为我浏览了一些页面并使用此解决方案测试了不同的场景,所有这些都运行良好。

    所以我修改了创建新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 会巧妙地请求新令牌。

    所以没问题,就像一个魅力。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-06
      • 2021-10-27
      • 2020-01-01
      • 2017-11-25
      • 2015-12-13
      • 2014-05-16
      • 1970-01-01
      相关资源
      最近更新 更多