|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2019 the original author or authors. |
|
|
|
* Copyright 2002-2024 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -18,35 +18,40 @@ package org.springframework.security.oauth2.client; |
|
|
|
|
|
|
|
|
|
|
|
import java.time.Duration; |
|
|
|
import java.time.Duration; |
|
|
|
import java.time.Instant; |
|
|
|
import java.time.Instant; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.BeforeEach; |
|
|
|
import org.junit.jupiter.api.BeforeEach; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.http.HttpStatus; |
|
|
|
import org.springframework.core.io.ClassPathResource; |
|
|
|
import org.springframework.http.RequestEntity; |
|
|
|
import org.springframework.http.HttpHeaders; |
|
|
|
import org.springframework.http.ResponseEntity; |
|
|
|
import org.springframework.http.MediaType; |
|
|
|
|
|
|
|
import org.springframework.http.converter.FormHttpMessageConverter; |
|
|
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
|
|
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
|
|
|
import org.springframework.security.core.Authentication; |
|
|
|
import org.springframework.security.core.Authentication; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient; |
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
|
|
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; |
|
|
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
|
|
|
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; |
|
|
|
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
|
|
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; |
|
|
|
import org.springframework.test.web.client.ExpectedCount; |
|
|
|
import org.springframework.web.client.RestOperations; |
|
|
|
import org.springframework.test.web.client.MockRestServiceServer; |
|
|
|
|
|
|
|
import org.springframework.web.client.RestClient; |
|
|
|
|
|
|
|
import org.springframework.web.client.RestTemplate; |
|
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
|
|
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
|
|
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
|
|
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
|
|
|
|
import static org.mockito.BDDMockito.given; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.times; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
|
|
|
|
import static org.springframework.test.web.client.ExpectedCount.once; |
|
|
|
|
|
|
|
import static org.springframework.test.web.client.ExpectedCount.times; |
|
|
|
|
|
|
|
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; |
|
|
|
|
|
|
|
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Tests for {@link OAuth2AuthorizedClientProviderBuilder}. |
|
|
|
* Tests for {@link OAuth2AuthorizedClientProviderBuilder}. |
|
|
|
@ -55,29 +60,30 @@ import static org.mockito.Mockito.verify; |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
|
|
|
|
|
|
|
|
private RestOperations accessTokenClient; |
|
|
|
private RestClientClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient; |
|
|
|
|
|
|
|
|
|
|
|
private DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient; |
|
|
|
private RestClientRefreshTokenTokenResponseClient refreshTokenTokenResponseClient; |
|
|
|
|
|
|
|
|
|
|
|
private DefaultRefreshTokenTokenResponseClient refreshTokenTokenResponseClient; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private DefaultPasswordTokenResponseClient passwordTokenResponseClient; |
|
|
|
private DefaultPasswordTokenResponseClient passwordTokenResponseClient; |
|
|
|
|
|
|
|
|
|
|
|
private Authentication principal; |
|
|
|
private Authentication principal; |
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
private MockRestServiceServer server; |
|
|
|
|
|
|
|
|
|
|
|
@BeforeEach |
|
|
|
@BeforeEach |
|
|
|
public void setup() { |
|
|
|
public void setup() { |
|
|
|
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); |
|
|
|
// TODO: Use of RestTemplate in these tests can be removed when
|
|
|
|
this.accessTokenClient = mock(RestOperations.class); |
|
|
|
// DefaultPasswordTokenResponseClient is removed.
|
|
|
|
given(this.accessTokenClient.exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class))) |
|
|
|
RestTemplate accessTokenClient = new RestTemplate( |
|
|
|
.willReturn(new ResponseEntity(accessTokenResponse, HttpStatus.OK)); |
|
|
|
List.of(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); |
|
|
|
this.refreshTokenTokenResponseClient = new DefaultRefreshTokenTokenResponseClient(); |
|
|
|
this.server = MockRestServiceServer.bindTo(accessTokenClient).build(); |
|
|
|
this.refreshTokenTokenResponseClient.setRestOperations(this.accessTokenClient); |
|
|
|
RestClient restClient = RestClient.create(accessTokenClient); |
|
|
|
this.clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient(); |
|
|
|
this.refreshTokenTokenResponseClient = new RestClientRefreshTokenTokenResponseClient(); |
|
|
|
this.clientCredentialsTokenResponseClient.setRestOperations(this.accessTokenClient); |
|
|
|
this.refreshTokenTokenResponseClient.setRestClient(restClient); |
|
|
|
|
|
|
|
this.clientCredentialsTokenResponseClient = new RestClientClientCredentialsTokenResponseClient(); |
|
|
|
|
|
|
|
this.clientCredentialsTokenResponseClient.setRestClient(restClient); |
|
|
|
this.passwordTokenResponseClient = new DefaultPasswordTokenResponseClient(); |
|
|
|
this.passwordTokenResponseClient = new DefaultPasswordTokenResponseClient(); |
|
|
|
this.passwordTokenResponseClient.setRestOperations(this.accessTokenClient); |
|
|
|
this.passwordTokenResponseClient.setRestOperations(accessTokenClient); |
|
|
|
this.principal = new TestingAuthenticationToken("principal", "password"); |
|
|
|
this.principal = new TestingAuthenticationToken("principal", "password"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -104,6 +110,8 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void buildWhenRefreshTokenProviderThenProviderReauthorizes() { |
|
|
|
public void buildWhenRefreshTokenProviderThenProviderReauthorizes() { |
|
|
|
|
|
|
|
mockAccessTokenResponse(once()); |
|
|
|
|
|
|
|
|
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
.refreshToken((configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient)) |
|
|
|
.refreshToken((configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient)) |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
@ -118,11 +126,13 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
OAuth2AuthorizedClient reauthorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
OAuth2AuthorizedClient reauthorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
assertThat(reauthorizedClient).isNotNull(); |
|
|
|
assertThat(reauthorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient).exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class)); |
|
|
|
this.server.verify(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void buildWhenClientCredentialsProviderThenProviderAuthorizes() { |
|
|
|
public void buildWhenClientCredentialsProviderThenProviderAuthorizes() { |
|
|
|
|
|
|
|
mockAccessTokenResponse(once()); |
|
|
|
|
|
|
|
|
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
.clientCredentials( |
|
|
|
.clientCredentials( |
|
|
|
(configurer) -> configurer.accessTokenResponseClient(this.clientCredentialsTokenResponseClient)) |
|
|
|
(configurer) -> configurer.accessTokenResponseClient(this.clientCredentialsTokenResponseClient)) |
|
|
|
@ -135,11 +145,13 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient).exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class)); |
|
|
|
this.server.verify(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void buildWhenPasswordProviderThenProviderAuthorizes() { |
|
|
|
public void buildWhenPasswordProviderThenProviderAuthorizes() { |
|
|
|
|
|
|
|
mockAccessTokenResponse(once()); |
|
|
|
|
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient)) |
|
|
|
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient)) |
|
|
|
@ -153,11 +165,13 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient).exchange(any(RequestEntity.class), eq(OAuth2AccessTokenResponse.class)); |
|
|
|
this.server.verify(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
public void buildWhenAllProvidersThenProvidersAuthorize() { |
|
|
|
public void buildWhenAllProvidersThenProvidersAuthorize() { |
|
|
|
|
|
|
|
mockAccessTokenResponse(times(3)); |
|
|
|
|
|
|
|
|
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() |
|
|
|
.authorizationCode() |
|
|
|
.authorizationCode() |
|
|
|
.refreshToken((configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient)) |
|
|
|
.refreshToken((configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient)) |
|
|
|
@ -184,8 +198,6 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
OAuth2AuthorizedClient reauthorizedClient = authorizedClientProvider.authorize(refreshTokenContext); |
|
|
|
OAuth2AuthorizedClient reauthorizedClient = authorizedClientProvider.authorize(refreshTokenContext); |
|
|
|
assertThat(reauthorizedClient).isNotNull(); |
|
|
|
assertThat(reauthorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient, times(1)).exchange(any(RequestEntity.class), |
|
|
|
|
|
|
|
eq(OAuth2AccessTokenResponse.class)); |
|
|
|
|
|
|
|
// client_credentials
|
|
|
|
// client_credentials
|
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
OAuth2AuthorizationContext clientCredentialsContext = OAuth2AuthorizationContext |
|
|
|
OAuth2AuthorizationContext clientCredentialsContext = OAuth2AuthorizationContext |
|
|
|
@ -195,8 +207,6 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
authorizedClient = authorizedClientProvider.authorize(clientCredentialsContext); |
|
|
|
authorizedClient = authorizedClientProvider.authorize(clientCredentialsContext); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient, times(2)).exchange(any(RequestEntity.class), |
|
|
|
|
|
|
|
eq(OAuth2AccessTokenResponse.class)); |
|
|
|
|
|
|
|
// password
|
|
|
|
// password
|
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
OAuth2AuthorizationContext passwordContext = OAuth2AuthorizationContext |
|
|
|
OAuth2AuthorizationContext passwordContext = OAuth2AuthorizationContext |
|
|
|
@ -208,8 +218,7 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
authorizedClient = authorizedClientProvider.authorize(passwordContext); |
|
|
|
authorizedClient = authorizedClientProvider.authorize(passwordContext); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
assertThat(authorizedClient).isNotNull(); |
|
|
|
verify(this.accessTokenClient, times(3)).exchange(any(RequestEntity.class), |
|
|
|
this.server.verify(); |
|
|
|
eq(OAuth2AccessTokenResponse.class)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
@Test |
|
|
|
@ -234,4 +243,10 @@ public class OAuth2AuthorizedClientProviderBuilderTests { |
|
|
|
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-1234", issuedAt, expiresAt); |
|
|
|
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token-1234", issuedAt, expiresAt); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void mockAccessTokenResponse(ExpectedCount expectedCount) { |
|
|
|
|
|
|
|
this.server.expect(expectedCount, requestTo("https://example.com/login/oauth/access_token")) |
|
|
|
|
|
|
|
.andRespond(withSuccess().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) |
|
|
|
|
|
|
|
.body(new ClassPathResource("access-token-response.json"))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|