diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java index 07d4697e..1fd5b58f 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,7 @@ import org.springframework.util.StringUtils; * * @author Dangzhicairang(小水牛) * @author Olga Maciaszek-Sharma + * @author Philipp Meier * @since 4.0.0 */ public class OAuth2AccessTokenInterceptor implements RequestInterceptor { @@ -122,7 +123,7 @@ public class OAuth2AccessTokenInterceptor implements RequestInterceptor { } Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { + if (principal == null || principal.getName() == null) { principal = ANONYMOUS_AUTHENTICATION; } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java index cec3fe95..9883d33a 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,10 @@ import feign.RequestTemplate; import feign.Target; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; @@ -43,6 +46,7 @@ import static org.mockito.Mockito.when; * * @author Dangzhicairang(小水牛) * @author Olga Maciaszek-Sharma + * @author Philipp Meier * */ class OAuth2AccessTokenInterceptorTests { @@ -78,7 +82,7 @@ class OAuth2AccessTokenInterceptorTests { void shouldAcquireValidToken() { oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); when(mockOAuth2AuthorizedClientManager - .authorize(argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) + .authorize(argThat(matchAuthorizeRequest("test")))) .thenReturn(validTokenOAuth2AuthorizedClient()); oAuth2AccessTokenInterceptor.apply(requestTemplate); @@ -89,7 +93,7 @@ class OAuth2AccessTokenInterceptorTests { @Test void shouldAcquireValidTokenFromServiceId() { when(mockOAuth2AuthorizedClientManager - .authorize(argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) + .authorize(argThat(matchAuthorizeRequest("test")))) .thenReturn(validTokenOAuth2AuthorizedClient()); oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); @@ -99,12 +103,12 @@ class OAuth2AccessTokenInterceptorTests { } @Test - void shouldAcquireValidTokenFromSpecifiedClientRegistrationId() { + void shouldAcquireValidTokenFromSpecifiedClientRegistrationIdPrincipalIsNull() { + SecurityContextHolder.getContext().setAuthentication(null); oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, mockOAuth2AuthorizedClientManager); when(mockOAuth2AuthorizedClientManager - .authorize(argThat((OAuth2AuthorizeRequest request) -> (DEFAULT_CLIENT_REGISTRATION_ID) - .equals(request.getClientRegistrationId())))) + .authorize(argThat(matchAuthorizeRequest(DEFAULT_CLIENT_REGISTRATION_ID)))) .thenReturn(validTokenOAuth2AuthorizedClient()); oAuth2AccessTokenInterceptor.apply(requestTemplate); @@ -112,13 +116,55 @@ class OAuth2AccessTokenInterceptorTests { assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token"); } + @Test + void shouldAcquireValidTokenFromSpecifiedClientRegistrationIdPrincipalNameIsNull() { + SecurityContextHolder.getContext().setAuthentication(principalWithName(null)); + oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, + mockOAuth2AuthorizedClientManager); + when(mockOAuth2AuthorizedClientManager + .authorize(argThat(matchAuthorizeRequest(DEFAULT_CLIENT_REGISTRATION_ID)))) + .thenReturn(validTokenOAuth2AuthorizedClient()); + + oAuth2AccessTokenInterceptor.apply(requestTemplate); + + assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token"); + } + + @Test + void shouldAcquireValidTokenFromSpecifiedClientRegistrationIdPrincipalNameIsNotNull() { + String principalName = "principalName"; + SecurityContextHolder.getContext().setAuthentication(principalWithName(principalName)); + oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, + mockOAuth2AuthorizedClientManager); + when(mockOAuth2AuthorizedClientManager + .authorize(argThat(matchAuthorizeRequestWithPrincipalName(DEFAULT_CLIENT_REGISTRATION_ID, principalName)))) + .thenReturn(validTokenOAuth2AuthorizedClienWithPrincipalName(principalName)); + + oAuth2AccessTokenInterceptor.apply(requestTemplate); + + assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token"); + } + + private ArgumentMatcher matchAuthorizeRequest(String clientRegistrationId) { + return matchAuthorizeRequestWithPrincipalName(clientRegistrationId, "anonymousUser"); + } + + private ArgumentMatcher matchAuthorizeRequestWithPrincipalName(String clientRegistrationId, String principalName) { + return (OAuth2AuthorizeRequest request) -> clientRegistrationId.equals(request.getClientRegistrationId()) + && principalName.equals(request.getPrincipal().getName()); + } + private OAuth2AccessToken validToken() { return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "Valid Token", Instant.now(), Instant.now().plusSeconds(60L)); } private OAuth2AuthorizedClient validTokenOAuth2AuthorizedClient() { - return new OAuth2AuthorizedClient(defaultClientRegistration(), "anonymousUser", validToken()); + return validTokenOAuth2AuthorizedClienWithPrincipalName("anonymousUser"); + } + + private OAuth2AuthorizedClient validTokenOAuth2AuthorizedClienWithPrincipalName(String principalName) { + return new OAuth2AuthorizedClient(defaultClientRegistration(), principalName, validToken()); } private ClientRegistration defaultClientRegistration() { @@ -129,4 +175,13 @@ class OAuth2AccessTokenInterceptorTests { .build(); } + private TestingAuthenticationToken principalWithName(String principalName) { + return new TestingAuthenticationToken(new java.security.Principal() { + @Override + public String getName() { + return principalName; + } + }, null); + } + }