mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-04 09:42:29 +00:00
Remove Resource Owner Password Credentials grant
Closes gh-17446
This commit is contained in:
parent
ee171a1c17
commit
cfe38957d7
@ -50,13 +50,11 @@ import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClie
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
@ -173,7 +171,6 @@ final class OAuth2ClientConfiguration {
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
PasswordOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerOAuth2AuthorizedClientProvider.class,
|
||||
TokenExchangeOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
@ -241,7 +238,6 @@ final class OAuth2ClientConfiguration {
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders
|
||||
.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
@ -331,24 +327,6 @@ final class OAuth2ClientConfiguration {
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -43,7 +43,6 @@ import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.DelegatingReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
@ -51,7 +50,6 @@ import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2Auth
|
||||
import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
@ -137,7 +135,6 @@ final class ReactiveOAuth2ClientConfiguration {
|
||||
AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenReactiveOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsReactiveOAuth2AuthorizedClientProvider.class,
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerReactiveOAuth2AuthorizedClientProvider.class,
|
||||
TokenExchangeReactiveOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
@ -212,7 +209,6 @@ final class ReactiveOAuth2ClientConfiguration {
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders
|
||||
.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
@ -301,24 +297,6 @@ final class ReactiveOAuth2ClientConfiguration {
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private ReactiveOAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordReactiveOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private ReactiveOAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<ReactiveOAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
|
@ -43,13 +43,11 @@ import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClie
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
@ -78,7 +76,6 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
PasswordOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerOAuth2AuthorizedClientProvider.class,
|
||||
TokenExchangeOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
@ -133,7 +130,6 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
|
||||
authorizedClientProviders.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
@ -225,24 +221,6 @@ final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegi
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -20,11 +20,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -48,7 +44,6 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
|
||||
@ -56,13 +51,11 @@ import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
@ -71,13 +64,11 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@ -237,50 +228,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
private void testPasswordGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
|
||||
.willReturn(accessTokenResponse);
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
|
||||
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2PasswordGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(grantRequest.getUsername()).isEqualTo("user");
|
||||
assertThat(grantRequest.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
@ -400,11 +347,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient() {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
@ -440,13 +382,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordOAuth2AuthorizedClientProvider passwordProvider() {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
@ -479,11 +414,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
.clientSecret("github-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.build(),
|
||||
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
|
||||
.clientId("facebook-client-id")
|
||||
.clientSecret("facebook-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build(),
|
||||
CommonOAuth2Provider.OKTA.getBuilder("okta")
|
||||
.clientId("okta-client-id")
|
||||
.clientSecret("okta-client-secret")
|
||||
@ -505,26 +435,6 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
return mock(OAuth2AuthorizedClientRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
|
||||
return (authorizedClientManager) -> authorizedClientManager
|
||||
.setContextAttributesMapper((authorizeRequest) -> {
|
||||
HttpServletRequest request = Objects
|
||||
.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
|
||||
String username = request.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
|
||||
Map<String, Object> attributes = Collections.emptyMap();
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
attributes = new HashMap<>();
|
||||
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockAccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -20,11 +20,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -34,7 +30,6 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
@ -47,7 +42,6 @@ import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2Authori
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.PasswordReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider;
|
||||
@ -56,14 +50,12 @@ import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2Authori
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
@ -73,13 +65,11 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -265,53 +255,6 @@ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
private void testPasswordGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
|
||||
.willReturn(Mono.just(accessTokenResponse));
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook")
|
||||
.block();
|
||||
assertThat(clientRegistration).isNotNull();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("username=user&password=password");
|
||||
this.exchange = MockServerWebExchange.builder(request).build();
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(ServerWebExchange.class.getName(), this.exchange)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block();
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2PasswordGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(grantRequest.getUsername()).isEqualTo("user");
|
||||
assertThat(grantRequest.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
@ -451,11 +394,6 @@ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
@ -491,13 +429,6 @@ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider password() {
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearer() {
|
||||
JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider();
|
||||
@ -530,11 +461,6 @@ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
|
||||
.clientSecret("github-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.build(),
|
||||
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
|
||||
.clientId("facebook-client-id")
|
||||
.clientSecret("facebook-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build(),
|
||||
CommonOAuth2Provider.OKTA.getBuilder("okta")
|
||||
.clientId("okta-client-id")
|
||||
.clientSecret("okta-client-secret")
|
||||
@ -551,29 +477,6 @@ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<DefaultReactiveOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
|
||||
return (authorizedClientManager) -> authorizedClientManager
|
||||
.setContextAttributesMapper((authorizeRequest) -> {
|
||||
ServerWebExchange exchange = Objects
|
||||
.requireNonNull(authorizeRequest.getAttribute(ServerWebExchange.class.getName()));
|
||||
return exchange.getFormData().map((parameters) -> {
|
||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
|
||||
Map<String, Object> attributes = Collections.emptyMap();
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
attributes = new HashMap<>();
|
||||
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockAccessTokenResponseClient<T extends AbstractOAuth2AuthorizationGrantRequest>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -20,12 +20,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -47,19 +43,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
@ -68,13 +61,11 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@ -235,50 +226,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.configLocations(xml("clients")).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.configLocations(xml("providers")).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
private void testPasswordGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
|
||||
.willReturn(accessTokenResponse);
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
|
||||
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2PasswordGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(grantRequest.getUsername()).isEqualTo("user");
|
||||
assertThat(grantRequest.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.configLocations(xml("clients")).autowire();
|
||||
@ -390,11 +337,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
|
||||
.clientSecret("github-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.build(),
|
||||
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
|
||||
.clientId("facebook-client-id")
|
||||
.clientSecret("facebook-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build(),
|
||||
CommonOAuth2Provider.OKTA.getBuilder("okta")
|
||||
.clientId("okta-client-id")
|
||||
.clientSecret("okta-client-secret")
|
||||
@ -411,24 +353,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
|
||||
return (authorizedClientManager) -> authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
|
||||
HttpServletRequest request = Objects
|
||||
.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
|
||||
String username = request.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
|
||||
Map<String, Object> attributes = Collections.emptyMap();
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
attributes = new HashMap<>();
|
||||
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
});
|
||||
}
|
||||
|
||||
public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCode() {
|
||||
return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
|
||||
}
|
||||
@ -453,16 +377,6 @@ public class OAuth2AuthorizedClientManagerRegistrarTests {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
public static PasswordOAuth2AuthorizedClientProvider password() {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(passwordAccessTokenResponseClient());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
public static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
return new MockAccessTokenResponseClient<>();
|
||||
}
|
||||
|
||||
public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient());
|
||||
|
@ -38,22 +38,16 @@
|
||||
</b:constructor-arg>
|
||||
</b:bean>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="authorizedClientManagerConsumer"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="refreshTokenAccessTokenResponseClient"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="clientCredentialsAccessTokenResponseClient"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="passwordAccessTokenResponseClient"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="jwtBearerAccessTokenResponseClient"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="tokenExchangeAccessTokenResponseClient"/>
|
||||
|
||||
</b:beans>
|
||||
</b:beans>
|
||||
|
@ -38,9 +38,6 @@
|
||||
</b:constructor-arg>
|
||||
</b:bean>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="authorizedClientManagerConsumer"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="authorizationCode"/>
|
||||
|
||||
@ -50,13 +47,10 @@
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="clientCredentials"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="password"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="jwtBearer"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
|
||||
factory-method="tokenExchange"/>
|
||||
|
||||
</b:beans>
|
||||
</b:beans>
|
||||
|
@ -493,7 +493,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Refresh Token grant.
|
||||
====
|
||||
|
||||
The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types.
|
||||
The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` grant type.
|
||||
If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it will automatically be refreshed by the `RefreshTokenReactiveOAuth2AuthorizedClientProvider`.
|
||||
|
||||
[[oauth2-client-client-credentials]]
|
||||
@ -698,264 +698,8 @@ class OAuth2ClientController {
|
||||
If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`.
|
||||
====
|
||||
|
||||
[[oauth2-client-password]]
|
||||
== [[oauth2Client-password-grant]]Resource Owner Password Credentials
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token]]
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `WebClientReactivePasswordTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The `WebClientReactivePasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated.
|
||||
This section will be removed in Spring Security 7.
|
||||
====
|
||||
|
||||
:section-id: password
|
||||
:grant-type: Password
|
||||
:class-name: WebClientReactivePasswordTokenResponseClient
|
||||
:grant-request: OAuth2PasswordGrantRequest
|
||||
:leveloffset: +1
|
||||
include::partial$reactive/oauth2/client/web-client-access-token-response-client.adoc[]
|
||||
|
||||
:leveloffset: -1
|
||||
|
||||
[[oauth2-client-password-authorized-client-provider-builder]]
|
||||
=== Customize using the Builder
|
||||
|
||||
Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you can configure it using the `ReactiveOAuth2AuthorizedClientProviderBuilder` (as an alternative to <<oauth2-client-password-access-token-response-client-bean,publishing a bean>>) as follows:
|
||||
|
||||
.Access Token Response Configuration via Builder
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
|
||||
val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password { it.accessTokenResponseClient(passwordTokenResponseClient) }
|
||||
.refreshToken()
|
||||
.build()
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
|
||||
which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-authorized-client-manager]]
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: password
|
||||
scope: read, write
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return Mono.just(contextAttributes);
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun authorizedClientManager(
|
||||
clientRegistrationRepository: ReactiveClientRegistrationRepository,
|
||||
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
|
||||
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<MutableMap<String, Any>>> {
|
||||
return Function { authorizeRequest ->
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
|
||||
val request: ServerHttpRequest = exchange.request
|
||||
val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
|
||||
val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
|
||||
}
|
||||
Mono.just(contextAttributes)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class OAuth2ClientController {
|
||||
|
||||
@Autowired
|
||||
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/")
|
||||
public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attribute(ServerWebExchange.class.getName(), exchange)
|
||||
.build();
|
||||
|
||||
return this.authorizedClientManager.authorize(authorizeRequest)
|
||||
.map(OAuth2AuthorizedClient::getAccessToken)
|
||||
// ...
|
||||
.thenReturn("index");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
class OAuth2ClientController {
|
||||
@Autowired
|
||||
private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
|
||||
|
||||
@GetMapping("/")
|
||||
fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
|
||||
val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attribute(ServerWebExchange::class.java.name, exchange)
|
||||
.build()
|
||||
|
||||
return authorizedClientManager.authorize(authorizeRequest)
|
||||
.map { it.accessToken }
|
||||
// ...
|
||||
.thenReturn("index")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`.
|
||||
====
|
||||
|
||||
[[oauth2-client-jwt-bearer]]
|
||||
== [[oauth2Client-jwt-bearer-grant]]JWT Bearer
|
||||
== JWT Bearer
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -56,7 +56,6 @@ It directly uses an xref:reactive/oauth2/client/core.adoc#oauth2Client-authorize
|
||||
* An `OAuth2AccessToken` will be requested if the client has not yet been authorized.
|
||||
** `authorization_code` - triggers the Authorization Request redirect to initiate the flow
|
||||
** `client_credentials` - the access token is obtained directly from the Token Endpoint
|
||||
** `password` - the access token is obtained directly from the Token Endpoint
|
||||
* If the `OAuth2AccessToken` is expired, it will be refreshed (or renewed) if a `ReactiveOAuth2AuthorizedClientProvider` is available to perform the authorization
|
||||
|
||||
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
|
||||
|
@ -51,7 +51,7 @@ public final class ClientRegistration {
|
||||
<4> `clientAuthenticationMethod`: The method used to authenticate the Client with the Provider.
|
||||
The supported values are *client_secret_basic*, *client_secret_post*, *private_key_jwt*, *client_secret_jwt* and *none* https://tools.ietf.org/html/rfc6749#section-2.1[(public clients)].
|
||||
<5> `authorizationGrantType`: The OAuth 2.0 Authorization Framework defines four https://tools.ietf.org/html/rfc6749#section-1.3[Authorization Grant] types.
|
||||
The supported values are `authorization_code`, `client_credentials`, `password`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
The supported values are `authorization_code`, `client_credentials`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
<6> `redirectUri`: The client's registered redirect URI that the _Authorization Server_ redirects the end-user's user-agent
|
||||
to after the end-user has authenticated and authorized access to the client.
|
||||
<7> `scopes`: The scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
|
||||
@ -255,7 +255,7 @@ Implementations will typically implement an authorization grant type, eg. `autho
|
||||
The default implementation of `ReactiveOAuth2AuthorizedClientManager` is `DefaultReactiveOAuth2AuthorizedClientManager`, which is associated with a `ReactiveOAuth2AuthorizedClientProvider` that may support multiple authorization grant types using a delegation-based composite.
|
||||
The `ReactiveOAuth2AuthorizedClientProviderBuilder` may be used to configure and build the delegation-based composite.
|
||||
|
||||
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@ -273,7 +273,6 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@ -297,7 +296,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
@ -312,7 +310,7 @@ In the case of a re-authorization failure, eg. a refresh token is no longer vali
|
||||
The default behaviour may be customized via `setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)`.
|
||||
|
||||
The `DefaultReactiveOAuth2AuthorizedClientManager` is also associated with a `contextAttributesMapper` of type `Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>>`, which is responsible for mapping attribute(s) from the `OAuth2AuthorizeRequest` to a `Map` of attributes to be associated to the `OAuth2AuthorizationContext`.
|
||||
This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProvider` with required (supported) attribute(s), eg. the `PasswordReactiveOAuth2AuthorizedClientProvider` requires the resource owner's `username` and `password` to be available in `OAuth2AuthorizationContext.getAttributes()`.
|
||||
This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProvider` with required (supported) attribute(s).
|
||||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
@ -329,7 +327,7 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
@ -338,7 +336,7 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// Assuming the attributes are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
@ -350,14 +348,12 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
String param1 = request.getQueryParams().getFirst("param1");
|
||||
String param2 = request.getQueryParams().getFirst("param2");
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
contextAttributes.put("param1", param1);
|
||||
contextAttributes.put("param2", param2);
|
||||
}
|
||||
return Mono.just(contextAttributes);
|
||||
};
|
||||
@ -373,15 +369,15 @@ fun authorizedClientManager(
|
||||
clientRegistrationRepository: ReactiveClientRegistrationRepository,
|
||||
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
// Assuming the attributes are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
@ -391,14 +387,12 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
|
||||
val request: ServerHttpRequest = exchange.request
|
||||
val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
|
||||
val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
val param1: String? = request.queryParams.getFirst("param1")
|
||||
val param2: String? = request.queryParams.getFirst("param2")
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
|
||||
contextAttributes["param1"] = param1!!
|
||||
contextAttributes["param2"] = param2!!
|
||||
}
|
||||
Mono.just(contextAttributes)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ At a high-level, the core features available are:
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-authorization-code[Authorization Code]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-refresh-token[Refresh Token]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-client-credentials[Client Credentials]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-password[Resource Owner Password Credentials]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-jwt-bearer[JWT Bearer]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-token-exchange[Token Exchange]
|
||||
|
||||
@ -81,7 +80,7 @@ class OAuth2ClientSecurityConfig {
|
||||
|
||||
The `ReactiveOAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `ReactiveOAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@ -99,7 +98,6 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@ -123,7 +121,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
|
@ -953,7 +953,6 @@ public class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider())
|
||||
.build();
|
||||
|
||||
@ -984,7 +983,6 @@ class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(JwtBearerReactiveOAuth2AuthorizedClientProvider())
|
||||
.build()
|
||||
|
||||
@ -1273,7 +1271,6 @@ Spring Security automatically resolves the following generic types of `ReactiveO
|
||||
* `OAuth2AuthorizationCodeGrantRequest` (see `WebClientReactiveAuthorizationCodeTokenResponseClient`)
|
||||
* `OAuth2RefreshTokenGrantRequest` (see `WebClientReactiveRefreshTokenTokenResponseClient`)
|
||||
* `OAuth2ClientCredentialsGrantRequest` (see `WebClientReactiveClientCredentialsTokenResponseClient`)
|
||||
* `OAuth2PasswordGrantRequest` (see `WebClientReactivePasswordTokenResponseClient`)
|
||||
* `JwtBearerGrantRequest` (see `WebClientReactiveJwtBearerTokenResponseClient`)
|
||||
* `TokenExchangeGrantRequest` (see `WebClientReactiveTokenExchangeTokenResponseClient`)
|
||||
|
||||
@ -1334,15 +1331,6 @@ public class SecurityConfig {
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
|
||||
new WebClientReactivePasswordTokenResponseClient();
|
||||
accessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
|
||||
WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
|
||||
@ -1400,14 +1388,6 @@ class SecurityConfig {
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
|
||||
accessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
|
||||
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
|
||||
@ -1477,10 +1457,6 @@ public class SecurityConfig {
|
||||
new WebClientReactiveClientCredentialsTokenResponseClient();
|
||||
clientCredentialsAccessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
WebClientReactivePasswordTokenResponseClient passwordAccessTokenResponseClient =
|
||||
new WebClientReactivePasswordTokenResponseClient();
|
||||
passwordAccessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
|
||||
new WebClientReactiveJwtBearerTokenResponseClient();
|
||||
jwtBearerAccessTokenResponseClient.setWebClient(webClient());
|
||||
@ -1506,9 +1482,6 @@ public class SecurityConfig {
|
||||
.clientCredentials((clientCredentials) -> clientCredentials
|
||||
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
)
|
||||
.password((password) -> password
|
||||
.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
)
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
@ -1557,9 +1530,6 @@ class SecurityConfig {
|
||||
val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
|
||||
clientCredentialsAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
val passwordAccessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
|
||||
passwordAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
|
||||
jwtBearerAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
@ -1580,9 +1550,6 @@ class SecurityConfig {
|
||||
.clientCredentials { clientCredentials ->
|
||||
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
}
|
||||
.password { password ->
|
||||
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
}
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
@ -546,7 +546,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Refresh Token grant.
|
||||
====
|
||||
|
||||
The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types.
|
||||
The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` grant type.
|
||||
If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it is automatically refreshed by the `RefreshTokenOAuth2AuthorizedClientProvider`.
|
||||
|
||||
[[oauth2-client-client-credentials]]
|
||||
@ -778,330 +778,8 @@ class OAuth2ClientController {
|
||||
If not provided, they default to `ServletRequestAttributes` by using `RequestContextHolder.getRequestAttributes()`.
|
||||
====
|
||||
|
||||
[[oauth2-client-password]]
|
||||
== [[oauth2Client-password-grant]]Resource Owner Password Credentials
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token]]
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
The default implementation of `OAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `DefaultPasswordTokenResponseClient`, which uses a `RestOperations` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The `DefaultPasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated.
|
||||
This section will be removed in Spring Security 7.
|
||||
====
|
||||
|
||||
The `DefaultPasswordTokenResponseClient` is flexible, as it lets you customize the pre-processing of the Token Request or post-handling of the Token Response.
|
||||
|
||||
[[oauth2-client-password-access-token-request]]
|
||||
=== Customizing the Access Token Request
|
||||
|
||||
If you need to customize the pre-processing of the Token Request, you can provide `DefaultPasswordTokenResponseClient.setRequestEntityConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, RequestEntity<?>>`.
|
||||
The default implementation (`OAuth2PasswordGrantRequestEntityConverter`) builds a `RequestEntity` representation of a standard https://tools.ietf.org/html/rfc6749#section-4.3.2[OAuth 2.0 Access Token Request].
|
||||
However, providing a custom `Converter` would let you extend the standard Token Request and add custom parameter(s).
|
||||
|
||||
To customize only the parameters of the request, you can provide `OAuth2PasswordGrantRequestEntityConverter.setParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>` to completely override the parameters sent with the request. This is often simpler than constructing a `RequestEntity` directly.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you prefer to only add additional parameters, you can provide `OAuth2PasswordGrantRequestEntityConverter.addParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
The custom `Converter` must return a valid `RequestEntity` representation of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token-response]]
|
||||
=== Customizing the Access Token Response
|
||||
|
||||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultPasswordTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(),
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
FormHttpMessageConverter(),
|
||||
OAuth2AccessTokenResponseHttpMessageConverter()))
|
||||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Spring MVC `FormHttpMessageConverter` is required, as it is used when sending the OAuth 2.0 Access Token Request.
|
||||
====
|
||||
|
||||
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
|
||||
You can provide `OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()` with a custom `Converter<Map<String, String>, OAuth2AccessTokenResponse>` that is used to convert the OAuth 2.0 Access Token Response parameters to an `OAuth2AccessTokenResponse`.
|
||||
|
||||
`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, such as `400 Bad Request`.
|
||||
It uses an `OAuth2ErrorHttpMessageConverter` to convert the OAuth 2.0 Error parameters to an `OAuth2Error`.
|
||||
|
||||
[[oauth2-client-password-authorized-client-provider-builder]]
|
||||
=== Customize using the Builder
|
||||
|
||||
Whether you customize `DefaultPasswordTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
.Access Token Response Configuration via Builder
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password { it.accessTokenResponseClient(passwordTokenResponseClient) }
|
||||
.refreshToken()
|
||||
.build()
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`OAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordOAuth2AuthorizedClientProvider`,
|
||||
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-authorized-client-manager]]
|
||||
=== Using the Access Token
|
||||
|
||||
Consider the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: password
|
||||
scope: read, write
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
Further consider the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun authorizedClientManager(
|
||||
clientRegistrationRepository: ClientRegistrationRepository,
|
||||
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
|
||||
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
|
||||
return Function { authorizeRequest ->
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
|
||||
val username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
|
||||
val password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
|
||||
}
|
||||
contextAttributes
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Given the preceding properties and bean, you can obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class OAuth2ClientController {
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Authentication authentication,
|
||||
HttpServletRequest servletRequest,
|
||||
HttpServletResponse servletResponse) {
|
||||
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attributes(attrs -> {
|
||||
attrs.put(HttpServletRequest.class.getName(), servletRequest);
|
||||
attrs.put(HttpServletResponse.class.getName(), servletResponse);
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
|
||||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
|
||||
|
||||
// ...
|
||||
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
class OAuth2ClientController {
|
||||
@Autowired
|
||||
private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager
|
||||
|
||||
@GetMapping("/")
|
||||
fun index(authentication: Authentication?,
|
||||
servletRequest: HttpServletRequest,
|
||||
servletResponse: HttpServletResponse): String {
|
||||
val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attributes(Consumer {
|
||||
it[HttpServletRequest::class.java.name] = servletRequest
|
||||
it[HttpServletResponse::class.java.name] = servletResponse
|
||||
})
|
||||
.build()
|
||||
val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
|
||||
val accessToken: OAuth2AccessToken = authorizedClient.accessToken
|
||||
|
||||
// ...
|
||||
|
||||
return "index"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes.
|
||||
If not provided, they default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`.
|
||||
====
|
||||
|
||||
[[oauth2-client-jwt-bearer]]
|
||||
== [[oauth2Client-jwt-bearer-grant]]JWT Bearer
|
||||
== JWT Bearer
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -60,7 +60,6 @@ The interceptor directly uses an `OAuth2AuthorizedClientManager` and therefore i
|
||||
* Performs an OAuth 2.0 Access Token request to obtain `OAuth2AccessToken` if the client has not yet been authorized
|
||||
** `authorization_code`: Triggers the Authorization Request redirect to initiate the flow
|
||||
** `client_credentials`: The access token is obtained directly from the Token Endpoint
|
||||
** `password`: The access token is obtained directly from the Token Endpoint
|
||||
** Additional grant types are supported by xref:servlet/oauth2/index.adoc#oauth2-client-enable-extension-grant-type[enabling extension grant types]
|
||||
* If an existing `OAuth2AccessToken` is expired, it is refreshed (or renewed)
|
||||
|
||||
@ -511,7 +510,6 @@ It directly uses an xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized
|
||||
* An `OAuth2AccessToken` is requested if the client has not yet been authorized.
|
||||
** `authorization_code`: Triggers the Authorization Request redirect to initiate the flow.
|
||||
** `client_credentials`: The access token is obtained directly from the Token Endpoint.
|
||||
** `password`: The access token is obtained directly from the Token Endpoint.
|
||||
* If the `OAuth2AccessToken` is expired, it is refreshed (or renewed) if an `OAuth2AuthorizedClientProvider` is available to perform the authorization
|
||||
|
||||
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
|
||||
|
@ -52,7 +52,7 @@ public final class ClientRegistration {
|
||||
<4> `clientAuthenticationMethod`: The method used to authenticate the Client with the Provider.
|
||||
The supported values are *client_secret_basic*, *client_secret_post*, *private_key_jwt*, *client_secret_jwt* and *none* https://tools.ietf.org/html/rfc6749#section-2.1[(public clients)].
|
||||
<5> `authorizationGrantType`: The OAuth 2.0 Authorization Framework defines four https://tools.ietf.org/html/rfc6749#section-1.3[Authorization Grant] types.
|
||||
The supported values are `authorization_code`, `client_credentials`, `password`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
The supported values are `authorization_code`, `client_credentials`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
<6> `redirectUri`: The client's registered redirect URI that the _Authorization Server_ redirects the end-user's user-agent
|
||||
to after the end-user has authenticated and authorized access to the client.
|
||||
<7> `scopes`: The scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
|
||||
@ -268,7 +268,7 @@ Implementations typically implement an authorization grant type, such as `author
|
||||
The default implementation of `OAuth2AuthorizedClientManager` is `DefaultOAuth2AuthorizedClientManager`, which is associated with an `OAuth2AuthorizedClientProvider` that may support multiple authorization grant types using a delegation-based composite.
|
||||
You can use `OAuth2AuthorizedClientProviderBuilder` to configure and build the delegation-based composite.
|
||||
|
||||
The following code shows an example of how to configure and build an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials`, and `password` authorization grant types:
|
||||
The following code shows an example of how to configure and build an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@ -286,7 +286,6 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@ -310,7 +309,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
@ -325,7 +323,7 @@ In the case of a re-authorization failure (for example, a refresh token is no lo
|
||||
You can customize the default behavior through `setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)`.
|
||||
|
||||
The `DefaultOAuth2AuthorizedClientManager` is also associated with a `contextAttributesMapper` of type `Function<OAuth2AuthorizeRequest, Map<String, Object>>`, which is responsible for mapping attribute(s) from the `OAuth2AuthorizeRequest` to a `Map` of attributes to be associated to the `OAuth2AuthorizationContext`.
|
||||
This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` with required (supported) attribute(s), eg. the `PasswordOAuth2AuthorizedClientProvider` requires the resource owner's `username` and `password` to be available in `OAuth2AuthorizationContext.getAttributes()`.
|
||||
This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` with required (supported) attribute(s).
|
||||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
@ -342,7 +340,7 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
@ -351,7 +349,7 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
|
||||
// Assuming the attributes are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
@ -362,14 +360,12 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
String param1 = servletRequest.getParameter("param1");
|
||||
String param2 = servletRequest.getParameter("param2");
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
contextAttributes.put("param1", param1);
|
||||
contextAttributes.put("param2", param2);
|
||||
}
|
||||
return contextAttributes;
|
||||
};
|
||||
@ -385,15 +381,15 @@ fun authorizedClientManager(
|
||||
clientRegistrationRepository: ClientRegistrationRepository,
|
||||
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
// Assuming the attributes are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
@ -402,14 +398,12 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableM
|
||||
return Function { authorizeRequest ->
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
|
||||
val username: String = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
|
||||
val password: String = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
val param1: String = servletRequest.getParameter("param1")
|
||||
val param2: String = servletRequest.getParameter("param2")
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
|
||||
contextAttributes["param1"] = param1
|
||||
contextAttributes["param2"] = param2
|
||||
}
|
||||
contextAttributes
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ At a high-level, the core features available are:
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-authorization-code[Authorization Code]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-refresh-token[Refresh Token]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-client-credentials[Client Credentials]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-password[Resource Owner Password Credentials]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-jwt-bearer[JWT Bearer]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-token-exchange[Token Exchange]
|
||||
|
||||
@ -104,7 +103,7 @@ The following code shows the complete configuration options available in the xre
|
||||
|
||||
The `OAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `OAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials`, and `password` authorization grant types:
|
||||
The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@ -122,7 +121,6 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@ -146,7 +144,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
|
@ -1367,7 +1367,6 @@ public class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
|
||||
.build();
|
||||
|
||||
@ -1398,7 +1397,6 @@ class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(JwtBearerOAuth2AuthorizedClientProvider())
|
||||
.build()
|
||||
|
||||
@ -1699,7 +1697,6 @@ Spring Security automatically resolves the following generic types of `OAuth2Acc
|
||||
* `OAuth2AuthorizationCodeGrantRequest` (see `DefaultAuthorizationCodeTokenResponseClient`)
|
||||
* `OAuth2RefreshTokenGrantRequest` (see `DefaultRefreshTokenTokenResponseClient`)
|
||||
* `OAuth2ClientCredentialsGrantRequest` (see `DefaultClientCredentialsTokenResponseClient`)
|
||||
* `OAuth2PasswordGrantRequest` (see `DefaultPasswordTokenResponseClient`)
|
||||
* `JwtBearerGrantRequest` (see `DefaultJwtBearerTokenResponseClient`)
|
||||
* `TokenExchangeGrantRequest` (see `DefaultTokenExchangeTokenResponseClient`)
|
||||
|
||||
@ -1760,15 +1757,6 @@ public class SecurityConfig {
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
DefaultPasswordTokenResponseClient accessTokenResponseClient =
|
||||
new DefaultPasswordTokenResponseClient();
|
||||
accessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
|
||||
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
|
||||
@ -1826,14 +1814,6 @@ class SecurityConfig {
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
|
||||
accessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
|
||||
val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
|
||||
@ -1917,10 +1897,6 @@ public class SecurityConfig {
|
||||
new DefaultClientCredentialsTokenResponseClient();
|
||||
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
|
||||
new DefaultPasswordTokenResponseClient();
|
||||
passwordAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
|
||||
new DefaultJwtBearerTokenResponseClient();
|
||||
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
@ -1946,9 +1922,6 @@ public class SecurityConfig {
|
||||
.clientCredentials((clientCredentials) -> clientCredentials
|
||||
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
)
|
||||
.password((password) -> password
|
||||
.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
)
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
@ -2012,9 +1985,6 @@ class SecurityConfig {
|
||||
val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
|
||||
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
|
||||
passwordAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
|
||||
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
@ -2035,9 +2005,6 @@ class SecurityConfig {
|
||||
.clientCredentials { clientCredentials ->
|
||||
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
}
|
||||
.password { password ->
|
||||
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
}
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2025 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,18 +48,6 @@ public final class OAuth2AuthorizationContext {
|
||||
public static final String REQUEST_SCOPE_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName()
|
||||
.concat(".REQUEST_SCOPE");
|
||||
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated
|
||||
* to the value for the resource owner's username.
|
||||
*/
|
||||
public static final String USERNAME_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".USERNAME");
|
||||
|
||||
/**
|
||||
* The name of the {@link #getAttribute(String) attribute} in the context associated
|
||||
* to the value for the resource owner's password.
|
||||
*/
|
||||
public static final String PASSWORD_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".PASSWORD");
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient;
|
||||
|
@ -28,7 +28,6 @@ import java.util.function.Consumer;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -36,10 +35,10 @@ import org.springframework.util.Assert;
|
||||
* A builder that builds a {@link DelegatingOAuth2AuthorizedClientProvider} composed of
|
||||
* one or more {@link OAuth2AuthorizedClientProvider}(s) that implement specific
|
||||
* authorization grants. The supported authorization grants are
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token},
|
||||
* {@link #clientCredentials() client_credentials} and {@link #password() password}. In
|
||||
* addition to the standard authorization grants, an implementation of an extension grant
|
||||
* may be supplied via {@link #provider(OAuth2AuthorizedClientProvider)}.
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token}
|
||||
* and {@link #clientCredentials() client_credentials}. In addition to the standard
|
||||
* authorization grants, an implementation of an extension grant may be supplied via
|
||||
* {@link #provider(OAuth2AuthorizedClientProvider)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@ -47,7 +46,6 @@ import org.springframework.util.Assert;
|
||||
* @see AuthorizationCodeOAuth2AuthorizedClientProvider
|
||||
* @see RefreshTokenOAuth2AuthorizedClientProvider
|
||||
* @see ClientCredentialsOAuth2AuthorizedClientProvider
|
||||
* @see PasswordOAuth2AuthorizedClientProvider
|
||||
* @see DelegatingOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
@ -135,38 +133,6 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public OAuth2AuthorizedClientProviderBuilder password() {
|
||||
this.builders.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for
|
||||
* further configuration
|
||||
* @return the {@link OAuth2AuthorizedClientProviderBuilder}
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public OAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuilder> builderConsumer) {
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders
|
||||
.computeIfAbsent(PasswordOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return OAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingOAuth2AuthorizedClientProvider} composed of
|
||||
* one or more {@link OAuth2AuthorizedClientProvider}(s).
|
||||
@ -186,79 +152,6 @@ public final class OAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code password} grant.
|
||||
*/
|
||||
public final class PasswordGrantBuilder implements Builder {
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private PasswordGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder accessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if
|
||||
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
* @see PasswordOAuth2AuthorizedClientProvider#setClockSkew(Duration)
|
||||
*/
|
||||
public PasswordGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link PasswordOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link PasswordOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AuthorizedClientProvider build() {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code client_credentials} grant.
|
||||
*/
|
||||
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AuthorizedClientProvider
|
||||
* @see DefaultPasswordTokenResponseClient
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public final class PasswordOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider {
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = new DefaultPasswordTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns {@code null} if authorization (or re-authorization) is not
|
||||
* supported, e.g. the client's {@link ClientRegistration#getAuthorizationGrantType()
|
||||
* authorization grant type} is not {@link AuthorizationGrantType#PASSWORD password}
|
||||
* OR the {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes are
|
||||
* not available in the provided {@code context} OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's password</li>
|
||||
* </ol>
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization (or
|
||||
* re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return null;
|
||||
}
|
||||
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
|
||||
return null;
|
||||
}
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized and access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return null;
|
||||
}
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
|
||||
&& authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh
|
||||
// token is available, than return and allow
|
||||
// RefreshTokenOAuth2AuthorizedClientProvider to handle the refresh
|
||||
return null;
|
||||
}
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
|
||||
password);
|
||||
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, passwordGrantRequest);
|
||||
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
try {
|
||||
return this.accessTokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
}
|
||||
catch (OAuth2AuthorizationException ex) {
|
||||
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(OAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code password} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code password} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
Assert.notNull(clockSkew, "clockSkew cannot be null");
|
||||
Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0");
|
||||
this.clockSkew = clockSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactivePasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AuthorizedClientProvider} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AuthorizedClientProvider
|
||||
* @see WebClientReactivePasswordTokenResponseClient
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public final class PasswordReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
|
||||
|
||||
private Duration clockSkew = Duration.ofSeconds(60);
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
* Attempt to authorize (or re-authorize) the
|
||||
* {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided
|
||||
* {@code context}. Returns an empty {@code Mono} if authorization (or
|
||||
* re-authorization) is not supported, e.g. the client's
|
||||
* {@link ClientRegistration#getAuthorizationGrantType() authorization grant type} is
|
||||
* not {@link AuthorizationGrantType#PASSWORD password} OR the
|
||||
* {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME username} and/or
|
||||
* {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME password} attributes are
|
||||
* not available in the provided {@code context} OR the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
|
||||
*
|
||||
* <p>
|
||||
* The following {@link OAuth2AuthorizationContext#getAttributes() context attributes}
|
||||
* are supported:
|
||||
* <ol>
|
||||
* <li>{@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's username</li>
|
||||
* <li>{@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a
|
||||
* {@code String} value for the resource owner's password</li>
|
||||
* </ol>
|
||||
* @param context the context that holds authorization-specific state for the client
|
||||
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if
|
||||
* authorization (or re-authorization) is not supported
|
||||
*/
|
||||
@Override
|
||||
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
|
||||
Assert.notNull(context, "context cannot be null");
|
||||
ClientRegistration clientRegistration = context.getClientRegistration();
|
||||
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
|
||||
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
|
||||
// If client is already authorized and access token is NOT expired than no
|
||||
// need for re-authorization
|
||||
return Mono.empty();
|
||||
}
|
||||
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
|
||||
&& authorizedClient.getRefreshToken() != null) {
|
||||
// If client is already authorized and access token is expired and a refresh
|
||||
// token is available,
|
||||
// than return and allow RefreshTokenReactiveOAuth2AuthorizedClientProvider to
|
||||
// handle the refresh
|
||||
return Mono.empty();
|
||||
}
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
|
||||
password);
|
||||
return Mono.just(passwordGrantRequest)
|
||||
.flatMap(this.accessTokenResponseClient::getTokenResponse)
|
||||
.onErrorMap(OAuth2AuthorizationException.class,
|
||||
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(), e))
|
||||
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
|
||||
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()));
|
||||
}
|
||||
|
||||
private boolean hasTokenExpired(OAuth2Token token) {
|
||||
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint for the {@code password} grant.
|
||||
* @param accessTokenResponseClient the client used when requesting an access token
|
||||
* credential at the Token Endpoint for the {@code password} grant
|
||||
*/
|
||||
public void setAccessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the
|
||||
* {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is
|
||||
* 60 seconds.
|
||||
*
|
||||
* <p>
|
||||
* An access token is considered expired if
|
||||
* {@code OAuth2AccessToken#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
*/
|
||||
public void setClockSkew(Duration clockSkew) {
|
||||
Assert.notNull(clockSkew, "clockSkew cannot be null");
|
||||
Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0");
|
||||
this.clockSkew = clockSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access
|
||||
* token expiry.
|
||||
* @param clock the clock
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "clock cannot be null");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,6 @@ import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.util.Assert;
|
||||
@ -35,10 +34,10 @@ import org.springframework.util.Assert;
|
||||
* A builder that builds a {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s) that
|
||||
* implement specific authorization grants. The supported authorization grants are
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token},
|
||||
* {@link #clientCredentials() client_credentials} and {@link #password() password}. In
|
||||
* addition to the standard authorization grants, an implementation of an extension grant
|
||||
* may be supplied via {@link #provider(ReactiveOAuth2AuthorizedClientProvider)}.
|
||||
* {@link #authorizationCode() authorization_code}, {@link #refreshToken() refresh_token}
|
||||
* and {@link #clientCredentials() client_credentials}. In addition to the standard
|
||||
* authorization grants, an implementation of an extension grant may be supplied via
|
||||
* {@link #provider(ReactiveOAuth2AuthorizedClientProvider)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
@ -46,7 +45,6 @@ import org.springframework.util.Assert;
|
||||
* @see AuthorizationCodeReactiveOAuth2AuthorizedClientProvider
|
||||
* @see RefreshTokenReactiveOAuth2AuthorizedClientProvider
|
||||
* @see ClientCredentialsReactiveOAuth2AuthorizedClientProvider
|
||||
* @see PasswordReactiveOAuth2AuthorizedClientProvider
|
||||
* @see DelegatingReactiveOAuth2AuthorizedClientProvider
|
||||
*/
|
||||
public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
@ -136,39 +134,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password() {
|
||||
this.builders.computeIfAbsent(PasswordReactiveOAuth2AuthorizedClientProvider.class,
|
||||
(k) -> new PasswordGrantBuilder());
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures support for the {@code password} grant.
|
||||
* @param builderConsumer a {@code Consumer} of {@link PasswordGrantBuilder} used for
|
||||
* further configuration
|
||||
* @return the {@link ReactiveOAuth2AuthorizedClientProviderBuilder}
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public ReactiveOAuth2AuthorizedClientProviderBuilder password(Consumer<PasswordGrantBuilder> builderConsumer) {
|
||||
PasswordGrantBuilder builder = (PasswordGrantBuilder) this.builders
|
||||
.computeIfAbsent(PasswordReactiveOAuth2AuthorizedClientProvider.class, (k) -> new PasswordGrantBuilder());
|
||||
builderConsumer.accept(builder);
|
||||
return ReactiveOAuth2AuthorizedClientProviderBuilder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link DelegatingReactiveOAuth2AuthorizedClientProvider}
|
||||
* composed of one or more {@link ReactiveOAuth2AuthorizedClientProvider}(s).
|
||||
@ -282,79 +247,6 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code password} grant.
|
||||
*/
|
||||
public final class PasswordGrantBuilder implements Builder {
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private Duration clockSkew;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private PasswordGrantBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client used when requesting an access token credential at the Token
|
||||
* Endpoint.
|
||||
* @param accessTokenResponseClient the client used when requesting an access
|
||||
* token credential at the Token Endpoint
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder accessTokenResponseClient(
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum acceptable clock skew, which is used when checking the access
|
||||
* token expiry. An access token is considered expired if
|
||||
* {@code OAuth2Token#getExpiresAt() - clockSkew} is before the current time
|
||||
* {@code clock#instant()}.
|
||||
* @param clockSkew the maximum acceptable clock skew
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
* @see PasswordReactiveOAuth2AuthorizedClientProvider#setClockSkew(Duration)
|
||||
*/
|
||||
public PasswordGrantBuilder clockSkew(Duration clockSkew) {
|
||||
this.clockSkew = clockSkew;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the
|
||||
* access token expiry.
|
||||
* @param clock the clock
|
||||
* @return the {@link PasswordGrantBuilder}
|
||||
*/
|
||||
public PasswordGrantBuilder clock(Clock clock) {
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an instance of {@link PasswordReactiveOAuth2AuthorizedClientProvider}.
|
||||
* @return the {@link PasswordReactiveOAuth2AuthorizedClientProvider}
|
||||
*/
|
||||
@Override
|
||||
public ReactiveOAuth2AuthorizedClientProvider build() {
|
||||
PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
}
|
||||
if (this.clockSkew != null) {
|
||||
authorizedClientProvider.setClockSkew(this.clockSkew);
|
||||
}
|
||||
if (this.clock != null) {
|
||||
authorizedClientProvider.setClock(this.clock);
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the {@code refresh_token} grant.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -55,7 +55,6 @@ import org.springframework.web.reactive.function.client.WebClient.RequestHeaders
|
||||
* Endpoint</a>
|
||||
* @see WebClientReactiveAuthorizationCodeTokenResponseClient
|
||||
* @see WebClientReactiveClientCredentialsTokenResponseClient
|
||||
* @see WebClientReactivePasswordTokenResponseClient
|
||||
* @see WebClientReactiveRefreshTokenTokenResponseClient
|
||||
* @see DefaultOAuth2TokenRequestHeadersConverter
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -35,7 +35,6 @@ import org.springframework.util.MultiValueMap;
|
||||
* <li>{@code authorization_code}</li>
|
||||
* <li>{@code refresh_token}</li>
|
||||
* <li>{@code client_credentials}</li>
|
||||
* <li>{@code password}</li>
|
||||
* <li>{@code urn:ietf:params:oauth:grant-type:jwt-bearer}</li>
|
||||
* <li>{@code urn:ietf:params:oauth:grant-type:token-exchange}</li>
|
||||
* </ul>
|
||||
@ -93,9 +92,6 @@ public final class DefaultOAuth2TokenRequestParametersConverter<T extends Abstra
|
||||
else if (grantRequest instanceof OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
|
||||
return OAuth2RefreshTokenGrantRequest.defaultParameters(refreshTokenGrantRequest);
|
||||
}
|
||||
else if (grantRequest instanceof OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
return OAuth2PasswordGrantRequest.defaultParameters(passwordGrantRequest);
|
||||
}
|
||||
else if (grantRequest instanceof JwtBearerGrantRequest jwtBearerGrantRequest) {
|
||||
return JwtBearerGrantRequest.defaultParameters(jwtBearerGrantRequest);
|
||||
}
|
||||
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* The default implementation of an {@link OAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant. This implementation uses a
|
||||
* {@link RestOperations} when requesting an access token credential at the Authorization
|
||||
* Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see OAuth2AccessTokenResponseClient
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public final class DefaultPasswordTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
|
||||
private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
|
||||
private Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public DefaultPasswordTokenResponseClient() {
|
||||
RestTemplate restTemplate = new RestTemplate(
|
||||
Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
Assert.notNull(passwordGrantRequest, "passwordGrantRequest cannot be null");
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(passwordGrantRequest);
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then we assume all requested scopes were
|
||||
// granted.
|
||||
// However, we use the explicit scopes returned in the response (if any).
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
|
||||
try {
|
||||
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
|
||||
+ ex.getMessage(),
|
||||
null);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the
|
||||
* {@link OAuth2PasswordGrantRequest} to a {@link RequestEntity} representation of the
|
||||
* OAuth 2.0 Access Token Request.
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a
|
||||
* {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RestOperations} used when requesting the OAuth 2.0 Access Token
|
||||
* Response.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
|
||||
* with the following:
|
||||
* <ol>
|
||||
* <li>{@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and
|
||||
* {@link OAuth2AccessTokenResponseHttpMessageConverter}</li>
|
||||
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
|
||||
* </ol>
|
||||
* @param restOperations the {@link RestOperations} used when requesting the Access
|
||||
* Token Response
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An OAuth 2.0 Resource Owner Password Credentials Grant request that holds the resource
|
||||
* owner's credentials.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see AbstractOAuth2AuthorizationGrantRequest
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-1.3.3">Section 1.3.3 Resource Owner
|
||||
* Password Credentials</a>
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public class OAuth2PasswordGrantRequest extends AbstractOAuth2AuthorizationGrantRequest {
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2PasswordGrantRequest} using the provided parameters.
|
||||
* @param clientRegistration the client registration
|
||||
* @param username the resource owner's username
|
||||
* @param password the resource owner's password
|
||||
*/
|
||||
public OAuth2PasswordGrantRequest(ClientRegistration clientRegistration, String username, String password) {
|
||||
super(AuthorizationGrantType.PASSWORD, clientRegistration);
|
||||
Assert.isTrue(AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType()),
|
||||
"clientRegistration.authorizationGrantType must be AuthorizationGrantType.PASSWORD");
|
||||
Assert.hasText(username, "username cannot be empty");
|
||||
Assert.hasText(password, "password cannot be empty");
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource owner's username.
|
||||
* @return the resource owner's username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource owner's password.
|
||||
* @return the resource owner's password
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate default parameters for the Password Grant.
|
||||
* @param grantRequest the authorization grant request
|
||||
* @return a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access
|
||||
* Token Request body
|
||||
*/
|
||||
static MultiValueMap<String, String> defaultParameters(OAuth2PasswordGrantRequest grantRequest) {
|
||||
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
|
||||
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " "));
|
||||
}
|
||||
parameters.set(OAuth2ParameterNames.USERNAME, grantRequest.getUsername());
|
||||
parameters.set(OAuth2ParameterNames.PASSWORD, grantRequest.getPassword());
|
||||
return parameters;
|
||||
}
|
||||
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractOAuth2AuthorizationGrantRequestEntityConverter}
|
||||
* that converts the provided {@link OAuth2PasswordGrantRequest} to a
|
||||
* {@link RequestEntity} representation of an OAuth 2.0 Access Token Request for the
|
||||
* Resource Owner Password Credentials Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see AbstractOAuth2AuthorizationGrantRequestEntityConverter
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see RequestEntity
|
||||
* @deprecated Use {@link DefaultOAuth2TokenRequestParametersConverter} instead
|
||||
*/
|
||||
@Deprecated(since = "6.4", forRemoval = true)
|
||||
public class OAuth2PasswordGrantRequestEntityConverter
|
||||
extends AbstractOAuth2AuthorizationGrantRequestEntityConverter<OAuth2PasswordGrantRequest> {
|
||||
|
||||
@Override
|
||||
protected MultiValueMap<String, String> createParameters(OAuth2PasswordGrantRequest passwordGrantRequest) {
|
||||
ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.add(OAuth2ParameterNames.GRANT_TYPE, passwordGrantRequest.getGrantType().getValue());
|
||||
parameters.add(OAuth2ParameterNames.USERNAME, passwordGrantRequest.getUsername());
|
||||
parameters.add(OAuth2ParameterNames.PASSWORD, passwordGrantRequest.getPassword());
|
||||
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
|
||||
parameters.add(OAuth2ParameterNames.SCOPE,
|
||||
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " "));
|
||||
}
|
||||
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) {
|
||||
parameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
|
||||
parameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} for the
|
||||
* {@link AuthorizationGrantType#PASSWORD password} grant. This implementation uses
|
||||
* {@link WebClient} when requesting an access token credential at the Authorization
|
||||
* Server's Token Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 5.2
|
||||
* @see ReactiveOAuth2AccessTokenResponseClient
|
||||
* @see OAuth2PasswordGrantRequest
|
||||
* @see OAuth2AccessTokenResponse
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.2">Section 4.3.2 Access Token Request
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://tools.ietf.org/html/rfc6749#section-4.3.3">Section 4.3.3 Access Token Response
|
||||
* (Resource Owner Password Credentials Grant)</a>
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public final class WebClientReactivePasswordTokenResponseClient
|
||||
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
|
||||
}
|
@ -66,9 +66,6 @@ abstract class StdConverters {
|
||||
if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.CLIENT_CREDENTIALS;
|
||||
}
|
||||
if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
|
||||
return AuthorizationGrantType.PASSWORD;
|
||||
}
|
||||
return new AuthorizationGrantType(value);
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ public final class ClientRegistration implements Serializable {
|
||||
|
||||
private static final List<AuthorizationGrantType> AUTHORIZATION_GRANT_TYPES = Arrays.asList(
|
||||
AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS,
|
||||
AuthorizationGrantType.REFRESH_TOKEN, AuthorizationGrantType.PASSWORD);
|
||||
AuthorizationGrantType.REFRESH_TOKEN);
|
||||
|
||||
private String registrationId;
|
||||
|
||||
@ -630,9 +630,6 @@ public final class ClientRegistration implements Serializable {
|
||||
if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(this.authorizationGrantType)) {
|
||||
this.validateClientCredentialsGrantType();
|
||||
}
|
||||
else if (AuthorizationGrantType.PASSWORD.equals(this.authorizationGrantType)) {
|
||||
this.validatePasswordGrantType();
|
||||
}
|
||||
else if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)) {
|
||||
this.validateAuthorizationCodeGrantType();
|
||||
}
|
||||
@ -697,14 +694,6 @@ public final class ClientRegistration implements Serializable {
|
||||
Assert.hasText(this.tokenUri, "tokenUri cannot be empty");
|
||||
}
|
||||
|
||||
private void validatePasswordGrantType() {
|
||||
Assert.isTrue(AuthorizationGrantType.PASSWORD.equals(this.authorizationGrantType),
|
||||
() -> "authorizationGrantType must be " + AuthorizationGrantType.PASSWORD.getValue());
|
||||
Assert.hasText(this.registrationId, "registrationId cannot be empty");
|
||||
Assert.hasText(this.clientId, "clientId cannot be empty");
|
||||
Assert.hasText(this.tokenUri, "tokenUri cannot be empty");
|
||||
}
|
||||
|
||||
private void validateAuthorizationGrantTypes() {
|
||||
for (AuthorizationGrantType authorizationGrantType : AUTHORIZATION_GRANT_TYPES) {
|
||||
if (authorizationGrantType.getValue().equalsIgnoreCase(this.authorizationGrantType.getValue())
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -92,7 +92,6 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -98,7 +98,6 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -18,7 +18,6 @@ package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -29,7 +28,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@ -40,7 +38,6 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2AccessToken
|
||||
import org.springframework.test.web.client.ExpectedCount;
|
||||
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.assertThatExceptionOfType;
|
||||
@ -64,26 +61,23 @@ public class OAuth2AuthorizedClientProviderBuilderTests {
|
||||
|
||||
private RestClientRefreshTokenTokenResponseClient refreshTokenTokenResponseClient;
|
||||
|
||||
private DefaultPasswordTokenResponseClient passwordTokenResponseClient;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
private MockRestServiceServer server;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
// TODO: Use of RestTemplate in these tests can be removed when
|
||||
// DefaultPasswordTokenResponseClient is removed.
|
||||
RestTemplate accessTokenClient = new RestTemplate(
|
||||
List.of(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
this.server = MockRestServiceServer.bindTo(accessTokenClient).build();
|
||||
RestClient restClient = RestClient.create(accessTokenClient);
|
||||
RestClient.Builder restClientBuilder = RestClient.builder().messageConverters((messageConverters) -> {
|
||||
messageConverters.clear();
|
||||
messageConverters.add(new FormHttpMessageConverter());
|
||||
messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
|
||||
});
|
||||
this.server = MockRestServiceServer.bindTo(restClientBuilder).build();
|
||||
RestClient restClient = restClientBuilder.build();
|
||||
this.refreshTokenTokenResponseClient = new RestClientRefreshTokenTokenResponseClient();
|
||||
this.refreshTokenTokenResponseClient.setRestClient(restClient);
|
||||
this.clientCredentialsTokenResponseClient = new RestClientClientCredentialsTokenResponseClient();
|
||||
this.clientCredentialsTokenResponseClient.setRestClient(restClient);
|
||||
this.passwordTokenResponseClient = new DefaultPasswordTokenResponseClient();
|
||||
this.passwordTokenResponseClient.setRestOperations(accessTokenClient);
|
||||
this.principal = new TestingAuthenticationToken("principal", "password");
|
||||
}
|
||||
|
||||
@ -148,36 +142,15 @@ public class OAuth2AuthorizedClientProviderBuilderTests {
|
||||
this.server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordProviderThenProviderAuthorizes() {
|
||||
mockAccessTokenResponse(once());
|
||||
|
||||
// @formatter:off
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient))
|
||||
.build();
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(TestClientRegistrations.password().build())
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
this.server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllProvidersThenProvidersAuthorize() {
|
||||
mockAccessTokenResponse(times(3));
|
||||
mockAccessTokenResponse(times(2));
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.authorizationCode()
|
||||
.refreshToken((configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient))
|
||||
.clientCredentials(
|
||||
(configurer) -> configurer.accessTokenResponseClient(this.clientCredentialsTokenResponseClient))
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient))
|
||||
.build();
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
|
||||
// authorization_code
|
||||
@ -207,17 +180,6 @@ public class OAuth2AuthorizedClientProviderBuilderTests {
|
||||
// @formatter:on
|
||||
authorizedClient = authorizedClientProvider.authorize(clientCredentialsContext);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
// password
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext passwordContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(TestClientRegistrations.password().build())
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
authorizedClient = authorizedClientProvider.authorize(passwordContext);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
this.server.verify();
|
||||
}
|
||||
|
||||
|
@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PasswordOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class PasswordOAuth2AuthorizedClientProviderTests {
|
||||
|
||||
private PasswordOAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
this.accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class);
|
||||
this.authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
this.clientRegistration = TestClientRegistrations.password().build();
|
||||
this.principal = new TestingAuthenticationToken("principal", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAccessTokenResponseClientWhenClientIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setAccessTokenResponseClient(null))
|
||||
.withMessage("accessTokenResponseClient cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockSkewWhenNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClockSkew(null))
|
||||
.withMessage("clockSkew cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockSkewWhenNegativeSecondsThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(-1)))
|
||||
.withMessage("clockSkew must be >= 0");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockWhenNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClock(null))
|
||||
.withMessage("clock cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.authorize(null))
|
||||
.withMessage("context cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenNotPasswordThenUnableToAuthorize() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build();
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(clientRegistration)
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedAndEmptyUsernameThenUnableToAuthorize() {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, null)
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedAndEmptyPasswordThenUnableToAuthorize() {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, null)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedThenAuthorize() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||
assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedWithoutRefreshTokenAndTokenExpiredThenReauthorize() {
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(60));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-expired", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), accessToken); // without refresh token
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||
assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedWithRefreshTokenAndTokenExpiredThenNotReauthorize() {
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(60));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-expired", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), accessToken, TestOAuth2RefreshTokens.refreshToken()); // with
|
||||
// refresh
|
||||
// token
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext)).isNull();
|
||||
}
|
||||
|
||||
// gh-7511
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedAndTokenNotExpiredButClockSkewForcesExpiryThenReauthorize() {
|
||||
Instant now = Instant.now();
|
||||
Instant issuedAt = now.minus(Duration.ofMinutes(60));
|
||||
Instant expiresAt = now.plus(Duration.ofMinutes(1));
|
||||
OAuth2AccessToken expiresInOneMinAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-1234", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), expiresInOneMinAccessToken); // without refresh
|
||||
// token
|
||||
// Shorten the lifespan of the access token by 90 seconds, which will ultimately
|
||||
// force it to expire on the client
|
||||
this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(90));
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
|
||||
assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PasswordReactiveOAuth2AuthorizedClientProvider}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class PasswordReactiveOAuth2AuthorizedClientProviderTests {
|
||||
|
||||
private PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private ClientRegistration clientRegistration;
|
||||
|
||||
private Authentication principal;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
|
||||
this.accessTokenResponseClient = mock(ReactiveOAuth2AccessTokenResponseClient.class);
|
||||
this.authorizedClientProvider.setAccessTokenResponseClient(this.accessTokenResponseClient);
|
||||
this.clientRegistration = TestClientRegistrations.password().build();
|
||||
this.principal = new TestingAuthenticationToken("principal", "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAccessTokenResponseClientWhenClientIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setAccessTokenResponseClient(null))
|
||||
.withMessage("accessTokenResponseClient cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockSkewWhenNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClockSkew(null))
|
||||
.withMessage("clockSkew cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockSkewWhenNegativeSecondsThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(-1)))
|
||||
.withMessage("clockSkew must be >= 0");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClockWhenNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.setClock(null))
|
||||
.withMessage("clock cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizedClientProvider.authorize(null).block())
|
||||
.withMessage("context cannot be null");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenNotPasswordThenUnableToAuthorize() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials().build();
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(clientRegistration)
|
||||
.principal(this.principal).
|
||||
build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedAndEmptyUsernameThenUnableToAuthorize() {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, null)
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedAndEmptyPasswordThenUnableToAuthorize() {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, null)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndNotAuthorizedThenAuthorize() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse));
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(this.clientRegistration)
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientProvider.authorize(authorizationContext).block();
|
||||
assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedWithoutRefreshTokenAndTokenExpiredThenReauthorize() {
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(60));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-expired", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), accessToken); // without refresh token
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse));
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext).block();
|
||||
assertThat(authorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedWithRefreshTokenAndTokenExpiredThenNotReauthorize() {
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofDays(1));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofMinutes(60));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-expired", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), accessToken, TestOAuth2RefreshTokens.refreshToken()); // with
|
||||
// refresh
|
||||
// token
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(this.authorizedClientProvider.authorize(authorizationContext).block()).isNull();
|
||||
}
|
||||
|
||||
// gh-7511
|
||||
@Test
|
||||
public void authorizeWhenPasswordAndAuthorizedAndTokenNotExpiredButClockSkewForcesExpiryThenReauthorize() {
|
||||
Instant now = Instant.now();
|
||||
Instant issuedAt = now.minus(Duration.ofMinutes(60));
|
||||
Instant expiresAt = now.minus(Duration.ofMinutes(1));
|
||||
OAuth2AccessToken expiresInOneMinAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
"access-token-1234", issuedAt, expiresAt);
|
||||
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration,
|
||||
this.principal.getName(), expiresInOneMinAccessToken); // without refresh
|
||||
// token
|
||||
// Shorten the lifespan of the access token by 90 seconds, which will ultimately
|
||||
// force it to expire on the client
|
||||
this.authorizedClientProvider.setClockSkew(Duration.ofSeconds(90));
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse));
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withAuthorizedClient(authorizedClient)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient reauthorizedClient = this.authorizedClientProvider.authorize(authorizationContext)
|
||||
.block();
|
||||
assertThat(reauthorizedClient.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(reauthorizedClient.getPrincipalName()).isEqualTo(this.principal.getName());
|
||||
assertThat(reauthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -145,33 +145,6 @@ public class ReactiveOAuth2AuthorizedClientProviderBuilderTests {
|
||||
assertThat(formParameters).contains("grant_type=client_credentials");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordProviderThenProviderAuthorizes() throws Exception {
|
||||
String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n" + " \"expires_in\": \"3600\"\n" + "}\n";
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder
|
||||
.builder()
|
||||
.password()
|
||||
.build();
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext authorizationContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(
|
||||
this.clientRegistrationBuilder.authorizationGrantType(AuthorizationGrantType.PASSWORD).build())
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
OAuth2AuthorizedClient authorizedClient = authorizedClientProvider.authorize(authorizationContext)
|
||||
.block();
|
||||
// @formatter:on
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
assertThat(this.server.getRequestCount()).isEqualTo(1);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllProvidersThenProvidersAuthorize() throws Exception {
|
||||
String accessTokenSuccessResponse = "{\n" + " \"access_token\": \"access-token-1234\",\n"
|
||||
@ -184,7 +157,6 @@ public class ReactiveOAuth2AuthorizedClientProviderBuilderTests {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
// authorization_code
|
||||
// @formatter:off
|
||||
@ -222,22 +194,6 @@ public class ReactiveOAuth2AuthorizedClientProviderBuilderTests {
|
||||
recordedRequest = this.server.takeRequest();
|
||||
formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=client_credentials");
|
||||
// password
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationContext passwordContext = OAuth2AuthorizationContext
|
||||
.withClientRegistration(
|
||||
this.clientRegistrationBuilder.authorizationGrantType(AuthorizationGrantType.PASSWORD).build())
|
||||
.principal(this.principal)
|
||||
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "username")
|
||||
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password")
|
||||
.build();
|
||||
// @formatter:on
|
||||
authorizedClient = authorizedClientProvider.authorize(passwordContext).block();
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
assertThat(this.server.getRequestCount()).isEqualTo(3);
|
||||
recordedRequest = this.server.takeRequest();
|
||||
formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=password");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -149,30 +149,6 @@ public class DefaultOAuth2TokenRequestParametersConverterTests {
|
||||
.containsExactly(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenGrantRequestIsPasswordThenParametersProvided() {
|
||||
ClientRegistration clientRegistration = this.clientRegistration
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build();
|
||||
OAuth2PasswordGrantRequest grantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user",
|
||||
"password");
|
||||
// @formatter:off
|
||||
DefaultOAuth2TokenRequestParametersConverter<OAuth2PasswordGrantRequest> parametersConverter =
|
||||
new DefaultOAuth2TokenRequestParametersConverter<>();
|
||||
// @formatter:on
|
||||
MultiValueMap<String, String> parameters = parametersConverter.convert(grantRequest);
|
||||
assertThat(parameters).hasSize(6);
|
||||
assertThat(parameters.get(OAuth2ParameterNames.GRANT_TYPE))
|
||||
.containsExactly(AuthorizationGrantType.PASSWORD.getValue());
|
||||
assertThat(parameters.get(OAuth2ParameterNames.CLIENT_ID)).containsExactly(clientRegistration.getClientId());
|
||||
assertThat(parameters.get(OAuth2ParameterNames.CLIENT_SECRET))
|
||||
.containsExactly(clientRegistration.getClientSecret());
|
||||
assertThat(parameters.get(OAuth2ParameterNames.USERNAME)).containsExactly(grantRequest.getUsername());
|
||||
assertThat(parameters.get(OAuth2ParameterNames.PASSWORD)).containsExactly(grantRequest.getPassword());
|
||||
assertThat(parameters.get(OAuth2ParameterNames.SCOPE))
|
||||
.containsExactly(StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenGrantRequestIsJwtBearerThenParametersProvided() {
|
||||
ClientRegistration clientRegistration = this.clientRegistration
|
||||
|
@ -1,317 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.jose.TestKeys;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultPasswordTokenResponseClient}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class DefaultPasswordTokenResponseClientTests {
|
||||
|
||||
private DefaultPasswordTokenResponseClient tokenResponseClient;
|
||||
|
||||
private ClientRegistration.Builder clientRegistration;
|
||||
|
||||
private String username = "user1";
|
||||
|
||||
private String password = "password";
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.tokenResponseClient = new DefaultPasswordTokenResponseClient();
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
String tokenUri = this.server.url("/oauth2/token").toString();
|
||||
// @formatter:off
|
||||
this.clientRegistration = TestClientRegistrations.password()
|
||||
.scope("read", "write")
|
||||
.tokenUri(tokenUri);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRequestEntityConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRequestEntityConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRestOperationsWhenRestOperationsIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setRestOperations(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\",\n"
|
||||
+ " \"scope\": \"read write\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
|
||||
ClientRegistration clientRegistration = this.clientRegistration.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString());
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
|
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=password");
|
||||
assertThat(formParameters).contains("username=user1");
|
||||
assertThat(formParameters).contains("password=password");
|
||||
assertThat(formParameters).contains("scope=read+write");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter);
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes())
|
||||
.containsExactly(clientRegistration.getScopes().toArray(new String[0]));
|
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenAuthenticationClientSecretPostThenFormParametersAreSent() throws Exception {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\",\n"
|
||||
+ " \"scope\": \"read\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
ClientRegistration clientRegistration = this.clientRegistration
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
||||
.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("client_id=client-id");
|
||||
assertThat(formParameters).contains("client_secret=client-secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenAuthenticationClientSecretJwtThenFormParametersAreSent() throws Exception {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
||||
.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
SecretKeySpec secretKey = new SecretKeySpec(
|
||||
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
JWK jwk = TestJwks.jwk(secretKey).build();
|
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk;
|
||||
configureJwtClientAuthenticationConverter(jwkResolver);
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters)
|
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer");
|
||||
assertThat(formParameters).contains("client_assertion=");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenAuthenticationPrivateKeyJwtThenFormParametersAreSent() throws Exception {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistration
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
JWK jwk = TestJwks.DEFAULT_RSA_JWK;
|
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk;
|
||||
configureJwtClientAuthenticationConverter(jwkResolver);
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters)
|
||||
.contains("client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer");
|
||||
assertThat(formParameters).contains("client_assertion=");
|
||||
}
|
||||
|
||||
private void configureJwtClientAuthenticationConverter(Function<ClientRegistration, JWK> jwkResolver) {
|
||||
NimbusJwtClientAuthenticationParametersConverter<OAuth2PasswordGrantRequest> jwtClientAuthenticationConverter = new NimbusJwtClientAuthenticationParametersConverter<>(
|
||||
jwkResolver);
|
||||
OAuth2PasswordGrantRequestEntityConverter requestEntityConverter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
requestEntityConverter.addParametersConverter(jwtClientAuthenticationConverter);
|
||||
this.tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"not-bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistration.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest))
|
||||
.withMessageContaining(
|
||||
"[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response")
|
||||
.havingRootCause()
|
||||
.withMessageContaining("tokenType cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() throws Exception {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\",\n"
|
||||
+ " \"scope\": \"read\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistration.build(), this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("scope=read");
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenAccessTokenHasNoScope() {
|
||||
// @formatter:off
|
||||
String accessTokenSuccessResponse = "{\n"
|
||||
+ " \"access_token\": \"access-token-1234\",\n"
|
||||
+ " \"token_type\": \"bearer\",\n"
|
||||
+ " \"expires_in\": \"3600\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
this.server.enqueue(jsonResponse(accessTokenSuccessResponse));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistration.build(), this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest);
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() {
|
||||
String accessTokenErrorResponse = "{\n" + " \"error\": \"unauthorized_client\"\n" + "}\n";
|
||||
this.server.enqueue(jsonResponse(accessTokenErrorResponse).setResponseCode(400));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistration.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest))
|
||||
.withMessageContaining("[unauthorized_client]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(500));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistration.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest))
|
||||
.withMessageContaining("[invalid_token_response] An error occurred while attempting to "
|
||||
+ "retrieve the OAuth 2.0 Access Token Response");
|
||||
}
|
||||
|
||||
private MockResponse jsonResponse(String json) {
|
||||
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json);
|
||||
}
|
||||
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2PasswordGrantRequestEntityConverter}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2PasswordGrantRequestEntityConverterTests {
|
||||
|
||||
private OAuth2PasswordGrantRequestEntityConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.converter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setHeadersConverter(null))
|
||||
.withMessage("headersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addHeadersConverter(null))
|
||||
.withMessage("headersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.setParametersConverter(null))
|
||||
.withMessage("parametersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.converter.addParametersConverter(null))
|
||||
.withMessage("parametersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenHeadersConverterSetThenCalled() {
|
||||
Converter<OAuth2PasswordGrantRequest, HttpHeaders> headersConverter1 = mock(Converter.class);
|
||||
this.converter.setHeadersConverter(headersConverter1);
|
||||
Converter<OAuth2PasswordGrantRequest, HttpHeaders> headersConverter2 = mock(Converter.class);
|
||||
this.converter.addHeadersConverter(headersConverter2);
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password().build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user1",
|
||||
"password");
|
||||
this.converter.convert(passwordGrantRequest);
|
||||
InOrder inOrder = inOrder(headersConverter1, headersConverter2);
|
||||
inOrder.verify(headersConverter1).convert(any(OAuth2PasswordGrantRequest.class));
|
||||
inOrder.verify(headersConverter2).convert(any(OAuth2PasswordGrantRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenParametersConverterSetThenCalled() {
|
||||
Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> parametersConverter1 = mock(
|
||||
Converter.class);
|
||||
this.converter.setParametersConverter(parametersConverter1);
|
||||
Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> parametersConverter2 = mock(
|
||||
Converter.class);
|
||||
this.converter.addParametersConverter(parametersConverter2);
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password().build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user1",
|
||||
"password");
|
||||
this.converter.convert(passwordGrantRequest);
|
||||
InOrder inOrder = inOrder(parametersConverter1, parametersConverter2);
|
||||
inOrder.verify(parametersConverter1).convert(any(OAuth2PasswordGrantRequest.class));
|
||||
inOrder.verify(parametersConverter2).convert(any(OAuth2PasswordGrantRequest.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void convertWhenGrantRequestValidThenConverts() {
|
||||
ClientRegistration clientRegistration = TestClientRegistrations.password().build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, "user1",
|
||||
"password");
|
||||
RequestEntity<?> requestEntity = this.converter.convert(passwordGrantRequest);
|
||||
assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST);
|
||||
assertThat(requestEntity.getUrl().toASCIIString())
|
||||
.isEqualTo(clientRegistration.getProviderDetails().getTokenUri());
|
||||
HttpHeaders headers = requestEntity.getHeaders();
|
||||
assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON);
|
||||
assertThat(headers.getContentType())
|
||||
.isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
|
||||
assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic ");
|
||||
MultiValueMap<String, String> formParameters = (MultiValueMap<String, String>) requestEntity.getBody();
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE))
|
||||
.isEqualTo(AuthorizationGrantType.PASSWORD.getValue());
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.USERNAME)).isEqualTo("user1");
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.PASSWORD)).isEqualTo("password");
|
||||
assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2PasswordGrantRequest}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2PasswordGrantRequestTests {
|
||||
|
||||
private ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build();
|
||||
|
||||
private String username = "user1";
|
||||
|
||||
private String password = "password";
|
||||
|
||||
@Test
|
||||
public void constructorWhenClientRegistrationIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(null, this.username, this.password))
|
||||
.withMessage("clientRegistration cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenUsernameIsEmptyThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(this.clientRegistration, null, this.password))
|
||||
.withMessage("username cannot be empty");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(this.clientRegistration, "", this.password))
|
||||
.withMessage("username cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenPasswordIsEmptyThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(this.clientRegistration, this.username, null))
|
||||
.withMessage("password cannot be empty");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(this.clientRegistration, this.username, ""))
|
||||
.withMessage("password cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenClientRegistrationInvalidGrantTypeThenThrowIllegalArgumentException() {
|
||||
ClientRegistration registration = TestClientRegistrations.clientCredentials().build();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2PasswordGrantRequest(registration, this.username, this.password))
|
||||
.withMessage("clientRegistration.authorizationGrantType must be AuthorizationGrantType.PASSWORD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenValidParametersProvidedThenCreated() {
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(this.clientRegistration,
|
||||
this.username, this.password);
|
||||
assertThat(passwordGrantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(passwordGrantRequest.getClientRegistration()).isSameAs(this.clientRegistration);
|
||||
assertThat(passwordGrantRequest.getUsername()).isEqualTo(this.username);
|
||||
assertThat(passwordGrantRequest.getPassword()).isEqualTo(this.password);
|
||||
}
|
||||
|
||||
}
|
@ -1,450 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.client.endpoint;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.security.oauth2.client.MockResponses;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.jose.TestKeys;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebClientReactivePasswordTokenResponseClient}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class WebClientReactivePasswordTokenResponseClientTests {
|
||||
|
||||
private WebClientReactivePasswordTokenResponseClient tokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder;
|
||||
|
||||
private String username = "user1";
|
||||
|
||||
private String password = "password";
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
String tokenUri = this.server.url("/oauth2/token").toString();
|
||||
this.clientRegistrationBuilder = TestClientRegistrations.password().tokenUri(tokenUri);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setWebClientWhenClientIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setWebClient(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenRequestIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.getTokenResponse(null).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseWithNoScope()
|
||||
throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest)
|
||||
.block();
|
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString());
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
|
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=password");
|
||||
assertThat(formParameters).contains("username=user1");
|
||||
assertThat(formParameters).contains("password=password");
|
||||
assertThat(formParameters).contains("scope=read+write");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter);
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).isEmpty();
|
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenReturnAccessTokenResponse() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response-read-write.json"));
|
||||
Instant expiresAtBefore = Instant.now().plusSeconds(3600);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest)
|
||||
.block();
|
||||
Instant expiresAtAfter = Instant.now().plusSeconds(3600);
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getMethod()).isEqualTo(HttpMethod.POST.toString());
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.ACCEPT)).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE))
|
||||
.isEqualTo(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("grant_type=password");
|
||||
assertThat(formParameters).contains("username=user1");
|
||||
assertThat(formParameters).contains("password=password");
|
||||
assertThat(formParameters).contains("scope=read+write");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234");
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER);
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter);
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes())
|
||||
.containsExactly(clientRegistration.getScopes().toArray(new String[0]));
|
||||
assertThat(accessTokenResponse.getRefreshToken()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenClientAuthenticationPostThenFormParametersAreSent() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
|
||||
.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block();
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("client_id=client-id");
|
||||
assertThat(formParameters).contains("client_secret=client-secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenAuthenticationClientSecretJwtThenFormParametersAreSent() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
||||
.clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
SecretKeySpec secretKey = new SecretKeySpec(
|
||||
clientRegistration.getClientSecret().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
JWK jwk = TestJwks.jwk(secretKey).build();
|
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk;
|
||||
configureJwtClientAuthenticationConverter(jwkResolver);
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block();
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
assertThat(actualRequest.getBody().readUtf8()).contains("grant_type=password",
|
||||
"client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer",
|
||||
"client_assertion=");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenAuthenticationPrivateKeyJwtThenFormParametersAreSent() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// Configure Jwt client authentication converter
|
||||
JWK jwk = TestJwks.DEFAULT_RSA_JWK;
|
||||
Function<ClientRegistration, JWK> jwkResolver = (registration) -> jwk;
|
||||
configureJwtClientAuthenticationConverter(jwkResolver);
|
||||
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block();
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
|
||||
assertThat(actualRequest.getBody().readUtf8()).contains("grant_type=password",
|
||||
"client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer",
|
||||
"client_assertion=");
|
||||
}
|
||||
|
||||
private void configureJwtClientAuthenticationConverter(Function<ClientRegistration, JWK> jwkResolver) {
|
||||
NimbusJwtClientAuthenticationParametersConverter<OAuth2PasswordGrantRequest> jwtClientAuthenticationConverter = new NimbusJwtClientAuthenticationParametersConverter<>(
|
||||
jwkResolver);
|
||||
this.tokenResponseClient.addParametersConverter(jwtClientAuthenticationConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() {
|
||||
this.server.enqueue(MockResponses.json("invalid-token-type-response.json"));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistrationBuilder.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block())
|
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response"))
|
||||
.withMessageContaining("[invalid_token_response]")
|
||||
.withMessageContaining("An error occurred parsing the Access Token response")
|
||||
.withCauseInstanceOf(Throwable.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessResponseIncludesScopeThenAccessTokenHasResponseScope() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response-read.json"));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistrationBuilder.build(), this.username, this.password);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.tokenResponseClient.getTokenResponse(passwordGrantRequest)
|
||||
.block();
|
||||
RecordedRequest recordedRequest = this.server.takeRequest();
|
||||
String formParameters = recordedRequest.getBody().readUtf8();
|
||||
assertThat(formParameters).contains("scope=read");
|
||||
assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenErrorResponseThenThrowOAuth2AuthorizationException() {
|
||||
this.server.enqueue(MockResponses.json("unauthorized-client-response.json").setResponseCode(400));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistrationBuilder.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block())
|
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("unauthorized_client"))
|
||||
.withMessageContaining("[unauthorized_client]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationException() {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(500));
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(
|
||||
this.clientRegistrationBuilder.build(), this.username, this.password);
|
||||
assertThatExceptionOfType(OAuth2AuthorizationException.class)
|
||||
.isThrownBy(() -> this.tokenResponseClient.getTokenResponse(passwordGrantRequest).block())
|
||||
.satisfies((ex) -> assertThat(ex.getError().getErrorCode()).isEqualTo("invalid_token_response"))
|
||||
.withMessageContaining("[invalid_token_response]")
|
||||
.withMessageContaining("Empty OAuth 2.0 Access Token Response");
|
||||
}
|
||||
|
||||
// gh-10130
|
||||
@Test
|
||||
public void setHeadersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setHeadersConverter(null))
|
||||
.withMessage("headersConverter cannot be null");
|
||||
}
|
||||
|
||||
// gh-10130
|
||||
@Test
|
||||
public void addHeadersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.addHeadersConverter(null))
|
||||
.withMessage("headersConverter cannot be null");
|
||||
}
|
||||
|
||||
// gh-10130
|
||||
@Test
|
||||
public void convertWhenHeadersConverterAddedThenCalled() throws Exception {
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
Converter<OAuth2PasswordGrantRequest, HttpHeaders> addedHeadersConverter = mock();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.put("custom-header-name", Collections.singletonList("custom-header-value"));
|
||||
given(addedHeadersConverter.convert(request)).willReturn(headers);
|
||||
this.tokenResponseClient.addHeadersConverter(addedHeadersConverter);
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
verify(addedHeadersConverter).convert(request);
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=");
|
||||
assertThat(actualRequest.getHeader("custom-header-name")).isEqualTo("custom-header-value");
|
||||
}
|
||||
|
||||
// gh-10130
|
||||
@Test
|
||||
public void convertWhenHeadersConverterSetThenCalled() throws Exception {
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
ClientRegistration clientRegistration = request.getClientRegistration();
|
||||
Converter<OAuth2PasswordGrantRequest, HttpHeaders> headersConverter = mock();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
|
||||
given(headersConverter.convert(request)).willReturn(headers);
|
||||
this.tokenResponseClient.setHeadersConverter(headersConverter);
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
verify(headersConverter).convert(request);
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION))
|
||||
.isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.setParametersConverter(null))
|
||||
.withMessage("parametersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.tokenResponseClient.addParametersConverter(null))
|
||||
.withMessage("parametersConverter cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenParametersConverterAddedThenCalled() throws Exception {
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> addedParametersConverter = mock();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.add("custom-parameter-name", "custom-parameter-value");
|
||||
given(addedParametersConverter.convert(request)).willReturn(parameters);
|
||||
this.tokenResponseClient.addParametersConverter(addedParametersConverter);
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
verify(addedParametersConverter).convert(request);
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
String formParameters = actualRequest.getBody().readUtf8();
|
||||
// @formatter:off
|
||||
assertThat(formParameters).contains(
|
||||
param(OAuth2ParameterNames.GRANT_TYPE, "password"),
|
||||
param("custom-parameter-name", "custom-parameter-value")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenParametersConverterSetThenCalled() throws Exception {
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> parametersConverter = mock();
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.add("custom-parameter-name", "custom-parameter-value");
|
||||
given(parametersConverter.convert(request)).willReturn(parameters);
|
||||
this.tokenResponseClient.setParametersConverter(parametersConverter);
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
verify(parametersConverter).convert(request);
|
||||
RecordedRequest actualRequest = this.server.takeRequest();
|
||||
assertThat(actualRequest.getBody().readUtf8()).contains("custom-parameter-name=custom-parameter-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenParametersConverterSetThenAbleToOverrideDefaultParameters() throws Exception {
|
||||
this.clientRegistrationBuilder.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST);
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, "custom");
|
||||
parameters.set(OAuth2ParameterNames.USERNAME, "user");
|
||||
parameters.set(OAuth2ParameterNames.PASSWORD, "password");
|
||||
parameters.set(OAuth2ParameterNames.SCOPE, "one two");
|
||||
this.tokenResponseClient.setParametersConverter((grantRequest) -> parameters);
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
String formParameters = this.server.takeRequest().getBody().readUtf8();
|
||||
// @formatter:off
|
||||
assertThat(formParameters).contains(
|
||||
param(OAuth2ParameterNames.GRANT_TYPE, "custom"),
|
||||
param(OAuth2ParameterNames.CLIENT_ID, "client-id"),
|
||||
param(OAuth2ParameterNames.SCOPE, "one two"),
|
||||
param(OAuth2ParameterNames.USERNAME, "user"),
|
||||
param(OAuth2ParameterNames.PASSWORD, "password")
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenResponseWhenParametersCustomizerSetThenCalled() throws Exception {
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
OAuth2PasswordGrantRequest request = new OAuth2PasswordGrantRequest(this.clientRegistrationBuilder.build(),
|
||||
this.username, this.password);
|
||||
Consumer<MultiValueMap<String, String>> parametersCustomizer = mock();
|
||||
this.tokenResponseClient.setParametersCustomizer(parametersCustomizer);
|
||||
this.tokenResponseClient.getTokenResponse(request).block();
|
||||
verify(parametersCustomizer).accept(any());
|
||||
}
|
||||
|
||||
// gh-10260
|
||||
@Test
|
||||
public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() {
|
||||
|
||||
WebClientReactivePasswordTokenResponseClient customClient = new WebClientReactivePasswordTokenResponseClient();
|
||||
|
||||
BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage> extractor = mock();
|
||||
OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(extractor.extract(any(), any())).willReturn(Mono.just(response));
|
||||
|
||||
customClient.setBodyExtractor(extractor);
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder.build();
|
||||
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration,
|
||||
this.username, this.password);
|
||||
|
||||
this.server.enqueue(MockResponses.json("access-token-response.json"));
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(passwordGrantRequest).block();
|
||||
assertThat(accessTokenResponse.getAccessToken()).isNotNull();
|
||||
|
||||
}
|
||||
|
||||
private static String param(String parameterName, String parameterValue) {
|
||||
return "%s=%s".formatted(parameterName, URLEncoder.encode(parameterValue, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -557,103 +557,6 @@ public class ClientRegistrationTests {
|
||||
.isThrownBy(() -> TestClientRegistrations.clientCredentials().scope("an\"invalid\"scope").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
// @formatter:off
|
||||
ClientRegistration registration = ClientRegistration.withRegistrationId(REGISTRATION_ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.scope(SCOPES.toArray(new String[0]))
|
||||
.tokenUri(TOKEN_URI)
|
||||
.clientName(CLIENT_NAME)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(registration.getRegistrationId()).isEqualTo(REGISTRATION_ID);
|
||||
assertThat(registration.getClientId()).isEqualTo(CLIENT_ID);
|
||||
assertThat(registration.getClientSecret()).isEqualTo(CLIENT_SECRET);
|
||||
assertThat(registration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(registration.getScopes()).isEqualTo(SCOPES);
|
||||
assertThat(registration.getProviderDetails().getTokenUri()).isEqualTo(TOKEN_URI);
|
||||
assertThat(registration.getClientName()).isEqualTo(CLIENT_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantRegistrationIdIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ClientRegistration.withRegistrationId(null)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.tokenUri(TOKEN_URI)
|
||||
.build()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantClientIdIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ClientRegistration
|
||||
.withRegistrationId(REGISTRATION_ID)
|
||||
.clientId(null)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.tokenUri(TOKEN_URI)
|
||||
.build()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantClientSecretIsNullThenDefaultToEmpty() {
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(null)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.tokenUri(TOKEN_URI)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(clientRegistration.getClientSecret()).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantClientAuthenticationMethodNotProvidedThenDefaultToBasic() {
|
||||
// @formatter:off
|
||||
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(REGISTRATION_ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.tokenUri(TOKEN_URI)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat(clientRegistration.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenPasswordGrantTokenUriIsNullThenThrowIllegalArgumentException() {
|
||||
// @formatter:off
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> ClientRegistration.withRegistrationId(REGISTRATION_ID)
|
||||
.clientId(CLIENT_ID)
|
||||
.clientSecret(CLIENT_SECRET)
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.tokenUri(null)
|
||||
.build()
|
||||
);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCustomGrantAllAttributesProvidedThenAllAttributesAreSet() {
|
||||
AuthorizationGrantType customGrantType = new AuthorizationGrantType("CUSTOM");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -73,17 +73,4 @@ public final class TestClientRegistrations {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static ClientRegistration.Builder password() {
|
||||
// @formatter:off
|
||||
return ClientRegistration.withRegistrationId("password")
|
||||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.scope("read", "write")
|
||||
.tokenUri("https://example.com/login/oauth/access_token")
|
||||
.clientName("Client Name")
|
||||
.clientId("client-id")
|
||||
.clientSecret("client-secret");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.security.oauth2.client.web;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@ -46,7 +45,6 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@ -309,45 +307,6 @@ public class DefaultOAuth2AuthorizedClientManagerTests {
|
||||
eq(this.request), eq(this.response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenRequestParameterUsernamePasswordThenMappedToContext() {
|
||||
given(this.clientRegistrationRepository.findByRegistrationId(eq(this.clientRegistration.getRegistrationId())))
|
||||
.willReturn(this.clientRegistration);
|
||||
given(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||
.willReturn(this.authorizedClient);
|
||||
// Set custom contextAttributesMapper
|
||||
this.authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
});
|
||||
this.request.addParameter(OAuth2ParameterNames.USERNAME, "username");
|
||||
this.request.addParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(this.clientRegistration.getRegistrationId())
|
||||
.principal(this.principal)
|
||||
.attributes((attrs) -> {
|
||||
attrs.put(HttpServletRequest.class.getName(), this.request);
|
||||
attrs.put(HttpServletResponse.class.getName(), this.response);
|
||||
})
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizedClientManager.authorize(authorizeRequest);
|
||||
verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture());
|
||||
OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue();
|
||||
String username = authorizationContext.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
assertThat(username).isEqualTo("username");
|
||||
String password = authorizationContext.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
assertThat(password).isEqualTo("password");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void reauthorizeWhenUnsupportedProviderThenNotReauthorized() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -17,8 +17,6 @@
|
||||
package org.springframework.security.oauth2.client.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Function;
|
||||
@ -35,7 +33,6 @@ import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.support.ExecutorServiceAdapter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
@ -467,42 +464,6 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests {
|
||||
verify(this.authorizedClientRepository, never()).removeAuthorizedClient(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenRequestFormParameterUsernamePasswordThenMappedToContext() {
|
||||
given(this.clientRegistrationRepository.findByRegistrationId(eq(this.clientRegistration.getRegistrationId())))
|
||||
.willReturn(Mono.just(this.clientRegistration));
|
||||
given(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class)))
|
||||
.willReturn(Mono.just(this.authorizedClient));
|
||||
// Set custom contextAttributesMapper capable of mapping the form parameters
|
||||
this.authorizedClientManager.setContextAttributesMapper(
|
||||
(authorizeRequest) -> currentServerWebExchange().flatMap(ServerWebExchange::getFormData)
|
||||
.map((formData) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
String username = formData.getFirst(OAuth2ParameterNames.USERNAME);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
String password = formData.getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
return contextAttributes;
|
||||
}));
|
||||
this.serverWebExchange = MockServerWebExchange
|
||||
.builder(MockServerHttpRequest.post("/")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("username=username&password=password"))
|
||||
.build();
|
||||
this.context = Context.of(ServerWebExchange.class, this.serverWebExchange);
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(this.clientRegistration.getRegistrationId())
|
||||
.principal(this.principal)
|
||||
.build();
|
||||
this.authorizedClientManager.authorize(authorizeRequest).contextWrite(this.context).block();
|
||||
verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture());
|
||||
OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue();
|
||||
String username = authorizationContext.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
|
||||
assertThat(username).isEqualTo("username");
|
||||
String password = authorizationContext.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
|
||||
assertThat(password).isEqualTo("password");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void reauthorizeWhenUnsupportedProviderThenNotReauthorized() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -17,8 +17,6 @@
|
||||
package org.springframework.security.oauth2.client.web.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -37,29 +35,23 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -88,8 +80,6 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
|
||||
|
||||
private ClientRegistration registration2;
|
||||
|
||||
private ClientRegistration registration3;
|
||||
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient1;
|
||||
@ -132,12 +122,9 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
|
||||
.scope("read", "write")
|
||||
.tokenUri("https://provider.com/oauth2/token")
|
||||
.build();
|
||||
this.registration3 = TestClientRegistrations.password()
|
||||
.registrationId("client3")
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1,
|
||||
this.registration2, this.registration3);
|
||||
this.registration2);
|
||||
this.authorizedClientRepository = mock(OAuth2AuthorizedClientRepository.class);
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.authorizationCode()
|
||||
@ -307,50 +294,6 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
|
||||
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void resolveArgumentWhenAuthorizedClientNotFoundForPasswordClientThenResolvesFromTokenResponseClient()
|
||||
throws Exception {
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = mock(
|
||||
OAuth2AccessTokenResponseClient.class);
|
||||
PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
passwordAuthorizedClientProvider.setAccessTokenResponseClient(passwordTokenResponseClient);
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(passwordAuthorizedClientProvider);
|
||||
// Set custom contextAttributesMapper
|
||||
authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
});
|
||||
this.argumentResolver = new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234")
|
||||
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||
.expiresIn(3600)
|
||||
.build();
|
||||
given(passwordTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(), any(HttpServletRequest.class)))
|
||||
.willReturn(null);
|
||||
MethodParameter methodParameter = this.getMethodParameter("passwordClient", OAuth2AuthorizedClient.class);
|
||||
this.request.setParameter(OAuth2ParameterNames.USERNAME, "username");
|
||||
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
OAuth2AuthorizedClient authorizedClient = (OAuth2AuthorizedClient) this.argumentResolver
|
||||
.resolveArgument(methodParameter, null, new ServletWebRequest(this.request, this.response), null);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
assertThat(authorizedClient.getClientRegistration()).isSameAs(this.registration3);
|
||||
assertThat(authorizedClient.getPrincipalName()).isEqualTo(this.principalName);
|
||||
assertThat(authorizedClient.getAccessToken()).isSameAs(accessTokenResponse.getAccessToken());
|
||||
verify(this.authorizedClientRepository).saveAuthorizedClient(eq(authorizedClient), eq(this.authentication),
|
||||
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
|
||||
Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
|
||||
return new MethodParameter(method, 0);
|
||||
@ -382,9 +325,6 @@ public class OAuth2AuthorizedClientArgumentResolverTests {
|
||||
@RegisteredOAuth2AuthorizedClient("client2") OAuth2AuthorizedClient authorizedClient) {
|
||||
}
|
||||
|
||||
void passwordClient(@RegisteredOAuth2AuthorizedClient("client3") OAuth2AuthorizedClient authorizedClient) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -43,7 +43,6 @@ import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.FormHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
@ -65,7 +64,6 @@ import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizationFailureHandler;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
|
||||
@ -74,7 +72,6 @@ import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClient
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@ -90,12 +87,10 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
@ -135,9 +130,6 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
@Mock
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient;
|
||||
|
||||
@Mock
|
||||
private ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient;
|
||||
|
||||
@Mock
|
||||
private ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient;
|
||||
|
||||
@ -181,7 +173,6 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
(configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient))
|
||||
.clientCredentials(
|
||||
(configurer) -> configurer.accessTokenResponseClient(this.clientCredentialsTokenResponseClient))
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient))
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.build();
|
||||
// @formatter:on
|
||||
@ -706,59 +697,6 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
verify(this.authorizationFailureHandler, never()).onAuthorizationFailure(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenPasswordClientNotAuthorizedThenGetNewToken() {
|
||||
setupMocks();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("test", "this");
|
||||
ClientRegistration registration = TestClientRegistrations.password().build();
|
||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("new-token")
|
||||
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||
.expiresIn(360)
|
||||
.build();
|
||||
given(this.passwordTokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse));
|
||||
given(this.clientRegistrationRepository.findByRegistrationId(eq(registration.getRegistrationId())))
|
||||
.willReturn(Mono.just(registration));
|
||||
given(this.authorizedClientRepository.loadAuthorizedClient(eq(registration.getRegistrationId()),
|
||||
eq(authentication), any()))
|
||||
.willReturn(Mono.empty());
|
||||
// Set custom contextAttributesMapper capable of mapping the form parameters
|
||||
this.authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
|
||||
ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
|
||||
return Mono.just(serverWebExchange).flatMap(ServerWebExchange::getFormData).map((formData) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
String username = formData.getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = formData.getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
});
|
||||
});
|
||||
this.serverWebExchange = MockServerWebExchange
|
||||
.builder(MockServerHttpRequest.post("/")
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.body("username=username&password=password"))
|
||||
.build();
|
||||
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.com"))
|
||||
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction
|
||||
.clientRegistrationId(registration.getRegistrationId()))
|
||||
.build();
|
||||
this.function.filter(request, this.exchange)
|
||||
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
|
||||
.contextWrite(serverWebExchange())
|
||||
.block();
|
||||
verify(this.passwordTokenResponseClient).getTokenResponse(any());
|
||||
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), eq(authentication), any());
|
||||
List<ClientRequest> requests = this.exchange.getRequests();
|
||||
assertThat(requests).hasSize(1);
|
||||
ClientRequest request1 = requests.get(0);
|
||||
assertThat(request1.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer new-token");
|
||||
assertThat(request1.url().toASCIIString()).isEqualTo("https://example.com");
|
||||
assertThat(request1.method()).isEqualTo(HttpMethod.GET);
|
||||
assertThat(getBody(request1)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenClientRegistrationIdThenAuthorizedClientResolved() {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", this.accessToken.getIssuedAt());
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -69,7 +69,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.oauth2.client.ClientAuthorizationException;
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
@ -79,7 +78,6 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
@ -95,14 +93,12 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@ -146,9 +142,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
@Mock
|
||||
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient;
|
||||
|
||||
@Mock
|
||||
private OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient;
|
||||
|
||||
@Mock
|
||||
private OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient;
|
||||
|
||||
@ -203,7 +196,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
(configurer) -> configurer.accessTokenResponseClient(this.refreshTokenTokenResponseClient))
|
||||
.clientCredentials(
|
||||
(configurer) -> configurer.accessTokenResponseClient(this.clientCredentialsTokenResponseClient))
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(this.passwordTokenResponseClient))
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.build();
|
||||
// @formatter:on
|
||||
@ -469,51 +461,6 @@ public class ServletOAuth2AuthorizedClientExchangeFilterFunctionTests {
|
||||
assertThat(getBody(request1)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenPasswordClientNotAuthorizedThenGetNewToken() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("new-token")
|
||||
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||
.expiresIn(360)
|
||||
.build();
|
||||
given(this.passwordTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse);
|
||||
ClientRegistration registration = TestClientRegistrations.password().build();
|
||||
given(this.clientRegistrationRepository.findByRegistrationId(eq(registration.getRegistrationId())))
|
||||
.willReturn(registration);
|
||||
// Set custom contextAttributesMapper
|
||||
this.authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
|
||||
Map<String, Object> contextAttributes = new HashMap<>();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
});
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||
servletRequest.setParameter(OAuth2ParameterNames.USERNAME, "username");
|
||||
servletRequest.setParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
|
||||
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("https://example.com"))
|
||||
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
|
||||
.clientRegistrationId(registration.getRegistrationId()))
|
||||
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.authentication(this.authentication))
|
||||
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.httpServletRequest(servletRequest))
|
||||
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.httpServletResponse(servletResponse))
|
||||
.build();
|
||||
this.function.filter(request, this.exchange).block();
|
||||
verify(this.passwordTokenResponseClient).getTokenResponse(any());
|
||||
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), eq(this.authentication), any(), any());
|
||||
List<ClientRequest> requests = this.exchange.getRequests();
|
||||
assertThat(requests).hasSize(1);
|
||||
ClientRequest request1 = requests.get(0);
|
||||
assertThat(request1.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer new-token");
|
||||
assertThat(request1.url().toASCIIString()).isEqualTo("https://example.com");
|
||||
assertThat(request1.method()).isEqualTo(HttpMethod.GET);
|
||||
assertThat(getBody(request1)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterWhenJwtBearerClientNotAuthorizedThenExchangeToken() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("exchanged-token")
|
||||
|
@ -27,9 +27,9 @@ import org.springframework.util.Assert;
|
||||
* access token.
|
||||
*
|
||||
* <p>
|
||||
* The OAuth 2.0 Authorization Framework defines four standard grant types: authorization
|
||||
* code, implicit, resource owner password credentials, and client credentials. It also
|
||||
* provides an extensibility mechanism for defining additional grant types.
|
||||
* The OAuth 2.0 Authorization Framework defines the standard grant types: authorization
|
||||
* code, refresh token and client credentials. It also provides an extensibility mechanism
|
||||
* for defining additional grant types.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
@ -47,15 +47,6 @@ public final class AuthorizationGrantType implements Serializable {
|
||||
|
||||
public static final AuthorizationGrantType CLIENT_CREDENTIALS = new AuthorizationGrantType("client_credentials");
|
||||
|
||||
/**
|
||||
* @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the
|
||||
* Resource Owner Password Credentials grant. See reference <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc9700#section-2.4">OAuth 2.0 Security Best
|
||||
* Current Practice.</a>
|
||||
*/
|
||||
@Deprecated(since = "5.8", forRemoval = true)
|
||||
public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password");
|
||||
|
||||
/**
|
||||
* @since 5.5
|
||||
*/
|
||||
|
@ -108,16 +108,6 @@ public final class OAuth2ParameterNames {
|
||||
*/
|
||||
public static final String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
/**
|
||||
* {@code username} - used in Access Token Request.
|
||||
*/
|
||||
public static final String USERNAME = "username";
|
||||
|
||||
/**
|
||||
* {@code password} - used in Access Token Request.
|
||||
*/
|
||||
public static final String PASSWORD = "password";
|
||||
|
||||
/**
|
||||
* {@code error} - used in Authorization Response and Access Token Response.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@ -43,11 +43,6 @@ public class AuthorizationGrantTypeTests {
|
||||
assertThat(AuthorizationGrantType.REFRESH_TOKEN.getValue()).isEqualTo("refresh_token");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueWhenPasswordGrantTypeThenReturnPassword() {
|
||||
assertThat(AuthorizationGrantType.PASSWORD.getValue()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueWhenJwtBearerGrantTypeThenReturnJwtBearer() {
|
||||
assertThat(AuthorizationGrantType.JWT_BEARER.getValue())
|
||||
|
@ -827,8 +827,10 @@ public final class SecurityMockServerConfigurers {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId("test")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.authorizationUri("https://authorize-uri.example.org")
|
||||
.tokenUri("https://token-uri.example.org");
|
||||
}
|
||||
|
||||
@ -988,8 +990,10 @@ public final class SecurityMockServerConfigurers {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId("test")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.authorizationUri("https://authorize-uri.example.org")
|
||||
.tokenUri("https://token-uri.example.org");
|
||||
}
|
||||
|
||||
@ -1140,9 +1144,11 @@ public final class SecurityMockServerConfigurers {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId(this.registrationId)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.clientSecret("test-secret")
|
||||
.authorizationUri("https://idp.example.org/oauth/authorize")
|
||||
.tokenUri("https://idp.example.org/oauth/token");
|
||||
}
|
||||
|
||||
|
@ -1369,8 +1369,10 @@ public final class SecurityMockMvcRequestPostProcessors {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId("test")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.authorizationUri("https://authorize-uri.example.org")
|
||||
.tokenUri("https://token-uri.example.org");
|
||||
}
|
||||
|
||||
@ -1504,8 +1506,10 @@ public final class SecurityMockMvcRequestPostProcessors {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId("test")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.authorizationUri("https://authorize-uri.example.org")
|
||||
.tokenUri("https://token-uri.example.org");
|
||||
}
|
||||
|
||||
@ -1627,9 +1631,11 @@ public final class SecurityMockMvcRequestPostProcessors {
|
||||
|
||||
private ClientRegistration.Builder clientRegistrationBuilder() {
|
||||
return ClientRegistration.withRegistrationId(this.registrationId)
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.redirectUri("https://client.example.com")
|
||||
.clientId("test-client")
|
||||
.clientSecret("test-secret")
|
||||
.authorizationUri("https://idp.example.org/oauth/authorize")
|
||||
.tokenUri("https://idp.example.org/oauth/token");
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user