Spring Security OAuth2 AnonymousUser - 不存储身份验证会话

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

这是我的第一篇文章,如果我错过任何标准的 SOF 实践,抱歉。如果我需要提供任何额外信息或者我提供的信息太多,请告诉我。

我一直在关注 Springboot 3 迁移文档以及此处的博客文章:https://davidagood.com/oauth-client-credentials-auto-refresh-spring/

从 Springboot 2.6 升级到 3.2 后,我发现 RestTemplate 现已被弃用,我切换到 WebClient,希望让我的 Springboot 应用程序按照我想要的方式运行。

就上下文而言,我使用的是 Springboot 3.2 Spring Security 6 Java 17 。我的应用程序是一个代理,它有一堆 API 端点,可以调用 Salesforce API。在从 RestTemplate 升级到 WebClient 之前,请求工作正常,但是,现在升级后,每个 API 调用都会收到多个身份验证调用,而不是存储会话并重用一个访问令牌,仅在其过期时刷新它。当我记录这些请求时,看起来它们没有进行身份验证,因此没有存储身份验证?我从我的请求中看到的“AnonymousRole”中收集到了这一点。

我认为 Salesforce 端的资源服务器不是问题,因为当我向同一 URL 发出邮递员请求时,我会返回正确的访问令牌:

请求

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=secret-placeholder&client_secret=secret-placeholder" \ "https://my.salesforce.com/services/oauth2/token"

回复

{"access_token":"{TOKEN_VALUE}","signature":"{SIGNATURE_VALUE}","scope":"api id","instance_url":"https://my.salesforce.com","id":"https://test.salesforce.com/id/{ID_VALUE}","token_type":"Bearer","issued_at":"{EPOCH_VALUE}"}

我相信博客“依赖 HTTP 会话来维护跨多个请求的上下文”中解决了这个“AnonymousUser”问题的可能原因。因此,我按照建议使用了“AuthorizedClientServiceOAuth2AuthorizedClientManager”来解决这个问题,但是不幸的是我仍然遇到同样的问题!我环顾四周,每个资源似乎都指向我所遵循的相同步骤,所以我在这一点上真的迷失了方向,我的高级工程师也无法弄清楚。

希望能得到一些帮助。非常感谢!

这是使用“.permitAll()”时的一些日志:

2024-01-29T15:59:11.615-08:00  INFO 53077 --- [crm-proxy] [oundedElastic-1] [                                                 ] c.s.c.c.s.c.SalesforceWebClientFactory   : Authorization successful for clientRegistrationId=salesforce, tokenUri=https://my.salesforce.com/services/oauth2/token
2024-01-29T15:59:12.610-08:00 DEBUG 53077 --- [crm-proxy] [nio-8080-exec-1] [65b83bcea50539ad048191ae13ee2d26-a244a0c27369d291] c.s.c.c.s.client.SalesforceClient        : took 2096ms to successfully GET to 'https://my.salesforce.com/services/data/v52.0/query?
2024-01-29T15:59:12.615-08:00 DEBUG 53077 --- [crm-proxy] [nio-8080-exec-1] [65b83bcea50539ad048191ae13ee2d26-a244a0c27369d291] c.s.c.c.s.client.SalesforceClient        : Security Context: SecurityContextImpl [Authentication=AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]]

这是旧的2.6代码:

@Component
public class SalesforceOAuth2RestTemplateFactory {
  public RestOperations generateOAuth2RestTemplate(SalesforceProperties salesforceProperties) {
    ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
    resource.setAccessTokenUri(salesforceProperties.getAccessTokenUri());
    resource.setClientId(salesforceProperties.getClientId());
    resource.setAuthenticationScheme(AuthenticationScheme.header);
    resource.setClientAuthenticationScheme(AuthenticationScheme.form);
    resource.setClientSecret(salesforceProperties.getClientSecret());
    resource.setGrantType("password");
    resource.setUsername(salesforceProperties.getUsername());
    resource.setPassword(salesforceProperties.getPassword());

    OAuth2RestTemplate oAuth2RestTemplate =
        new OAuth2RestTemplate(
            resource, new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
    oAuth2RestTemplate.setRetryBadAccessTokens(true);
    oAuth2RestTemplate.setErrorHandler(new SalesforceOAuth2ErrorHandler(resource));

    return oAuth2RestTemplate;
  }

这是我当前的3.2代码:

(活动资料:本地) (环境名称:dev01)

我已在此在线剪贴板链接中粘贴了以下类,因为如果我尝试将代码粘贴到此处,我的帖子会被标记为垃圾邮件,抱歉:SalesforceWebClientFactory.java、SalesforceClient.java、UserController.java、application.yaml、build。 gradle.kts:

https://ctxt.io/2/AABwsvBOFw

java spring-boot spring-security oauth-2.0
1个回答
0
投票

我注意到您来自 Salesforce API 的访问令牌响应不提供

expires_in
字段,仅提供
issued_at
字段。

这是预期行为,因为根据规范推荐使用

expires_in
https://datatracker.ietf.org/doc/html/rfc6749#section-5.1

深入研究 Spring Security 代码,发现如果缺少

expires_in
字段,则过期时间将默认为
issued_at
时间之后的 1 秒。因此,导致您看到访问令牌不断刷新的问题,即使它不一定无效。

要解决此问题,您需要通过重写

getTokenResponse()
来添加自定义令牌处理,实现如下所示:

public class CustomClientCredentialsTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {

private final DefaultClientCredentialsTokenResponseClient delegate =
            new DefaultClientCredentialsTokenResponseClient();

    @Override
    public OAuth2AccessTokenResponse getTokenResponse(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) throws OAuth2AuthenticationException {

        var response = delegate.getTokenResponse(clientCredentialsGrantRequest);

        // expiration time of 1 hour (3600 seconds)
        long defaultExpiresInSeconds = 3600L;
        Instant now = Instant.now();

        // here is where you update the expiration
        Instant expiresAt = now.plusSeconds(defaultExpiresInSeconds);

        // rebuild the OAuth2AccessToken with the new expiration time
        OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
                response.getAccessToken().getTokenValue(), now, expiresAt, response.getAccessToken().getScopes());

        return OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
                .tokenType(accessToken.getTokenType())
                .expiresIn(defaultExpiresInSeconds)
                .scopes(accessToken.getScopes())
                .additionalParameters(response.getAdditionalParameters())
                .build();
    }

}

然后确保将此处理程序添加到现有的

authorizedClientManager
中,如下所示:

public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService) {

    …
    
    OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials(clientCredentialsGrantBuilder ->
    clientCredentialsGrantBuilder.accessTokenResponseClient(
    // here is where you reference your custom token handling
    new
    CustomClientCredentialsTokenResponseClient()))
    .build();

}

这应该可以解决问题。您拥有的其他所有内容看起来都很好,并且此方法可以与

WebClient
RestClient
实现一起使用,因为两者都使用相同的
authorizedClientManager
。让我知道这是否有效,希望有帮助。 :)

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