0

我见过类似的线程,但我的不同之处在于我使用的是自定义grant type. 为您提供背景知识,当我们从另一个微服务调用微服务时,我们使用一个delegation令牌,其中包含发起调用的用户的详细信息。因此,用户U1调用S1S1调用S2,以便S2U1详细信息用于审计和许可目的。

现在要实现这一点,我们有以下配置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每个用户,并将缓存到期设置为小于令牌到期的值。尽管缓存过期并不是真正的问题,因为每个令牌都会在得到这一点之前进行验证。

现在的问题是我如何实现这一目标,最好的方法是什么?根据我的理解,我需要将范围从默认 Spring bean 的范围更改requestsingleton类似,然后在缓存中没有条目时以某种方式使其创建一个新实例?不知道如何做到这一点?

4

1 回答 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都会巧妙地请求新令牌。

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

于 2020-04-29T01:10:41.577 回答