Remove Resource Owner Password Credentials grant

Closes gh-17446
This commit is contained in:
Joe Grandja 2025-07-02 17:12:47 -04:00
parent ee171a1c17
commit cfe38957d7
54 changed files with 103 additions and 3996 deletions

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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>

View File

@ -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>

View File

@ -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());

View File

@ -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>

View File

@ -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>

View File

@ -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 Servers 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]
====

View File

@ -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:

View File

@ -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)
}

View File

@ -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)

View File

@ -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()

View File

@ -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 Servers 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]
====

View File

@ -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:

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -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;

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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.
*/

View File

@ -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
*/

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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");

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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) {
}
}
}

View File

@ -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());

View File

@ -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")

View File

@ -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
*/

View File

@ -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.
*/

View File

@ -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())

View File

@ -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");
}

View File

@ -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");
}