diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java index 0bdd2bfe34..7cfab9dfbc 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java @@ -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 authorizedClientProviders) { - PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider(); - } - - OAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, - OAuth2PasswordGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( Collection authorizedClientProviders) { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java index 7432a9c565..d61bdd3e9c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientConfiguration.java @@ -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 authorizedClientProviders) { - PasswordReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, PasswordReactiveOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new PasswordReactiveOAuth2AuthorizedClientProvider(); - } - - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class, - OAuth2PasswordGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - private ReactiveOAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( Collection authorizedClientProviders) { JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( diff --git a/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java b/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java index dc131adcd4..2d291afcb7 100644 --- a/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java +++ b/config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java @@ -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 authorizedClientProviders) { - PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( - authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class); - if (authorizedClientProvider == null) { - authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider(); - } - - OAuth2AccessTokenResponseClient accessTokenResponseClient = getBeanOfType( - ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, - OAuth2PasswordGrantRequest.class)); - if (accessTokenResponseClient != null) { - authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient); - } - - return authorizedClientProvider; - } - private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider( Collection authorizedClientProviders) { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType( diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java index 7e58ce5b8e..cb1c80e1b7 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java @@ -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 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 passwordTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - @Bean OAuth2AccessTokenResponseClient 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 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 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 diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java index dd7698e98d..3db9b0b33d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java @@ -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 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 passwordAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - @Bean ReactiveOAuth2AccessTokenResponseClient 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 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 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 diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java index adeb9d8cb0..24a502e724 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java @@ -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 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 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 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 passwordAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient()); diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml index c0a9c67156..17c48190b6 100644 --- a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml @@ -38,22 +38,16 @@ - - - - - \ No newline at end of file + diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml index 0f167f5ed1..f3d9a934ae 100644 --- a/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml +++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml @@ -38,9 +38,6 @@ - - @@ -50,13 +47,10 @@ - - - \ No newline at end of file + diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc index 15ea7789e0..f40f3dee9d 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc @@ -493,7 +493,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Refresh Token grant. ==== -The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types. +The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` grant type. If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it will automatically be refreshed by the `RefreshTokenReactiveOAuth2AuthorizedClientProvider`. [[oauth2-client-client-credentials]] @@ -698,264 +698,8 @@ class OAuth2ClientController { If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`. ==== -[[oauth2-client-password]] -== [[oauth2Client-password-grant]]Resource Owner Password Credentials - -[NOTE] -==== -Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant. -==== - -[[oauth2-client-password-access-token]] -=== Requesting an Access Token - -[NOTE] -==== -Please refer to the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant. -==== - -The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `WebClientReactivePasswordTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint. - -[CAUTION] -==== -The `WebClientReactivePasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated. -This section will be removed in Spring Security 7. -==== - -:section-id: password -:grant-type: Password -:class-name: WebClientReactivePasswordTokenResponseClient -:grant-request: OAuth2PasswordGrantRequest -:leveloffset: +1 -include::partial$reactive/oauth2/client/web-client-access-token-response-client.adoc[] - -:leveloffset: -1 - -[[oauth2-client-password-authorized-client-provider-builder]] -=== Customize using the Builder - -Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you can configure it using the `ReactiveOAuth2AuthorizedClientProviderBuilder` (as an alternative to <>) as follows: - -.Access Token Response Configuration via Builder -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -// Customize -ReactiveOAuth2AccessTokenResponseClient passwordTokenResponseClient = ... - -ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = - ReactiveOAuth2AuthorizedClientProviderBuilder.builder() - .password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient)) - .refreshToken() - .build(); - -// ... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient = ... - -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>> contextAttributesMapper() { - return authorizeRequest -> { - Map 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>> { - return Function { authorizeRequest -> - var contextAttributes: MutableMap = 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 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 { - 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] ==== diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/authorized-clients.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/authorized-clients.adoc index 42391468b3..058363cec5 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/authorized-clients.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/authorized-clients.adoc @@ -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: diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc index e1ca19df49..d95a8f35da 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc @@ -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>>`, 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>> contextAttri Map 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 = 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) } diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc index 2260f7d5ae..1f46b8ba8f 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/index.adoc @@ -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) diff --git a/docs/modules/ROOT/pages/reactive/oauth2/index.adoc b/docs/modules/ROOT/pages/reactive/oauth2/index.adoc index 2cec8f296e..81fc85dfda 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/index.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/index.adoc @@ -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 passwordAccessTokenResponseClient() { - WebClientReactivePasswordTokenResponseClient accessTokenResponseClient = - new WebClientReactivePasswordTokenResponseClient(); - accessTokenResponseClient.setWebClient(webClient()); - - return accessTokenResponseClient; - } - @Bean public ReactiveOAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient = @@ -1400,14 +1388,6 @@ class SecurityConfig { return accessTokenResponseClient } - @Bean - fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { - val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient() - accessTokenResponseClient.setWebClient(webClient()) - - return accessTokenResponseClient - } - @Bean fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient { 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() diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc index ce0323c123..d8ea7f1bb4 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc @@ -546,7 +546,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) which is an implementation of an `OAuth2AuthorizedClientProvider` for the Refresh Token grant. ==== -The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types. +The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` grant type. If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it is automatically refreshed by the `RefreshTokenOAuth2AuthorizedClientProvider`. [[oauth2-client-client-credentials]] @@ -778,330 +778,8 @@ class OAuth2ClientController { If not provided, they default to `ServletRequestAttributes` by using `RequestContextHolder.getRequestAttributes()`. ==== -[[oauth2-client-password]] -== [[oauth2Client-password-grant]]Resource Owner Password Credentials - -[NOTE] -==== -See the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant. -==== - -[[oauth2-client-password-access-token]] -=== Requesting an Access Token - -[NOTE] -==== -See the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant. -==== - -The default implementation of `OAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `DefaultPasswordTokenResponseClient`, which uses a `RestOperations` when requesting an access token at the Authorization Server’s Token Endpoint. - -[CAUTION] -==== -The `DefaultPasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated. -This section will be removed in Spring Security 7. -==== - -The `DefaultPasswordTokenResponseClient` is flexible, as it lets you customize the pre-processing of the Token Request or post-handling of the Token Response. - -[[oauth2-client-password-access-token-request]] -=== Customizing the Access Token Request - -If you need to customize the pre-processing of the Token Request, you can provide `DefaultPasswordTokenResponseClient.setRequestEntityConverter()` with a custom `Converter>`. -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>` 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>` 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, 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 passwordTokenResponseClient = ... - -OAuth2AuthorizedClientProvider authorizedClientProvider = - OAuth2AuthorizedClientProviderBuilder.builder() - .password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient)) - .refreshToken() - .build(); - -// ... - -authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -val passwordTokenResponseClient: OAuth2AccessTokenResponseClient = ... - -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> contextAttributesMapper() { - return authorizeRequest -> { - Map 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> { - return Function { authorizeRequest -> - var contextAttributes: MutableMap = 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] ==== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc index aa721f1413..43b2c24e7a 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorized-clients.adoc @@ -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: diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc index 0418877371..ba0291c068 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc @@ -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>`, 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> contextAttributesM return authorizeRequest -> { Map 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 var contextAttributes: MutableMap = 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 } diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc index 3ef66f0fef..7a1c9a740c 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/index.adoc @@ -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) diff --git a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc index 57039f26ad..732267449f 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/index.adoc @@ -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 passwordAccessTokenResponseClient() { - DefaultPasswordTokenResponseClient accessTokenResponseClient = - new DefaultPasswordTokenResponseClient(); - accessTokenResponseClient.setRestOperations(restTemplate()); - - return accessTokenResponseClient; - } - @Bean public OAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { DefaultJwtBearerTokenResponseClient accessTokenResponseClient = @@ -1826,14 +1814,6 @@ class SecurityConfig { return accessTokenResponseClient } - @Bean - fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { - val accessTokenResponseClient = DefaultPasswordTokenResponseClient() - accessTokenResponseClient.setRestOperations(restTemplate()) - - return accessTokenResponseClient - } - @Bean fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient { 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() diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java index 35a670f995..37eab2c82d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java @@ -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; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java index 74026e7e56..7601ec129c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java @@ -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 OAuth 2.0 Security Best - * Current Practice. - */ - @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 OAuth 2.0 Security Best - * Current Practice. - */ - @Deprecated(since = "5.8", forRemoval = true) - public OAuth2AuthorizedClientProviderBuilder password(Consumer 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 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 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. */ diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProvider.java deleted file mode 100644 index c3c80284af..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProvider.java +++ /dev/null @@ -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 OAuth 2.0 Security Best - * Current Practice. - */ -@Deprecated(since = "5.8", forRemoval = true) -public final class PasswordOAuth2AuthorizedClientProvider implements OAuth2AuthorizedClientProvider { - - private OAuth2AccessTokenResponseClient 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. - * - *

- * The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} - * are supported: - *

    - *
  1. {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a - * {@code String} value for the resource owner's username
  2. - *
  3. {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a - * {@code String} value for the resource owner's password
  4. - *
- * @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 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. - * - *

- * 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProvider.java deleted file mode 100644 index b3229a8b01..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProvider.java +++ /dev/null @@ -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 OAuth 2.0 Security Best - * Current Practice. - */ -@Deprecated(since = "5.8", forRemoval = true) -public final class PasswordReactiveOAuth2AuthorizedClientProvider implements ReactiveOAuth2AuthorizedClientProvider { - - private ReactiveOAuth2AccessTokenResponseClient 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. - * - *

- * The following {@link OAuth2AuthorizationContext#getAttributes() context attributes} - * are supported: - *

    - *
  1. {@link OAuth2AuthorizationContext#USERNAME_ATTRIBUTE_NAME} (required) - a - * {@code String} value for the resource owner's username
  2. - *
  3. {@link OAuth2AuthorizationContext#PASSWORD_ATTRIBUTE_NAME} (required) - a - * {@code String} value for the resource owner's password
  4. - *
- * @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 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 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. - * - *

- * 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java index f75de34304..7183853cff 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java @@ -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 OAuth 2.0 Security Best - * Current Practice. - */ - @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 OAuth 2.0 Security Best - * Current Practice. - */ - @Deprecated(since = "5.8", forRemoval = true) - public ReactiveOAuth2AuthorizedClientProviderBuilder password(Consumer 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 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 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. */ diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index b92e050c80..faf62bb569 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -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 * @see WebClientReactiveAuthorizationCodeTokenResponseClient * @see WebClientReactiveClientCredentialsTokenResponseClient - * @see WebClientReactivePasswordTokenResponseClient * @see WebClientReactiveRefreshTokenTokenResponseClient * @see DefaultOAuth2TokenRequestHeadersConverter */ diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java index 70a396f7c3..219d5f7c80 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java @@ -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; *

  • {@code authorization_code}
  • *
  • {@code refresh_token}
  • *
  • {@code client_credentials}
  • - *
  • {@code password}
  • *
  • {@code urn:ietf:params:oauth:grant-type:jwt-bearer}
  • *
  • {@code urn:ietf:params:oauth:grant-type:token-exchange}
  • * @@ -93,9 +92,6 @@ public final class DefaultOAuth2TokenRequestParametersConverterSection 4.3.2 Access Token Request - * (Resource Owner Password Credentials Grant) - * @see Section 4.3.3 Access Token Response - * (Resource Owner Password Credentials Grant) - * @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the - * Resource Owner Password Credentials grant. See reference OAuth 2.0 Security Best - * Current Practice. - */ -@Deprecated(since = "5.8", forRemoval = true) -public final class DefaultPasswordTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - private static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response"; - - private Converter> 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 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 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> 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. - * - *

    - * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

      - *
    1. {@link HttpMessageConverter}'s - {@link FormHttpMessageConverter} and - * {@link OAuth2AccessTokenResponseHttpMessageConverter}
    2. - *
    3. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
    4. - *
    - * @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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequest.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequest.java deleted file mode 100644 index 48f78f8628..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequest.java +++ /dev/null @@ -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 Section 1.3.3 Resource Owner - * Password Credentials - * @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the - * Resource Owner Password Credentials grant. See reference OAuth 2.0 Security Best - * Current Practice. - */ -@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 defaultParameters(OAuth2PasswordGrantRequest grantRequest) { - ClientRegistration clientRegistration = grantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverter.java deleted file mode 100644 index 1c0fc4aad9..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverter.java +++ /dev/null @@ -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 { - - @Override - protected MultiValueMap createParameters(OAuth2PasswordGrantRequest passwordGrantRequest) { - ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration(); - MultiValueMap 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; - } - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java deleted file mode 100644 index ff16eb855f..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java +++ /dev/null @@ -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 Section 4.3.2 Access Token Request - * (Resource Owner Password Credentials Grant) - * @see Section 4.3.3 Access Token Response - * (Resource Owner Password Credentials Grant) - * @deprecated The OAuth 2.0 Security Best Current Practice disallows the use of the - * Resource Owner Password Credentials grant. See reference OAuth 2.0 Security Best - * Current Practice. - */ -@Deprecated(since = "5.8", forRemoval = true) -public final class WebClientReactivePasswordTokenResponseClient - extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient { - -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java index b544f26793..e14af05086 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java @@ -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); } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java index b492a6d801..7a3b2c06ed 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java @@ -346,7 +346,7 @@ public final class ClientRegistration implements Serializable { private static final List 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()) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java index 0059382ba1..576bbe554c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java @@ -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 diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java index 17e4c1c7b4..56368b9d4e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java @@ -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 diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilderTests.java index 16b996d8a4..87c7b7164b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilderTests.java @@ -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(); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProviderTests.java deleted file mode 100644 index 835bed7965..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordOAuth2AuthorizedClientProviderTests.java +++ /dev/null @@ -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 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()); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProviderTests.java deleted file mode 100644 index acc1c004ef..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/PasswordReactiveOAuth2AuthorizedClientProviderTests.java +++ /dev/null @@ -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 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()); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilderTests.java index 33d0d58a73..4e415fb89b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilderTests.java @@ -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 diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java index 541c0a8086..b504d359e7 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverterTests.java @@ -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 parametersConverter = - new DefaultOAuth2TokenRequestParametersConverter<>(); - // @formatter:on - MultiValueMap 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 diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java deleted file mode 100644 index 62ea7dc2c3..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/DefaultPasswordTokenResponseClientTests.java +++ /dev/null @@ -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 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 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 jwkResolver) { - NimbusJwtClientAuthenticationParametersConverter 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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java deleted file mode 100644 index a9de0ceba7..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestEntityConverterTests.java +++ /dev/null @@ -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 headersConverter1 = mock(Converter.class); - this.converter.setHeadersConverter(headersConverter1); - Converter 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> parametersConverter1 = mock( - Converter.class); - this.converter.setParametersConverter(parametersConverter1); - Converter> 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 formParameters = (MultiValueMap) 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()); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestTests.java deleted file mode 100644 index 1d929a95f6..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2PasswordGrantRequestTests.java +++ /dev/null @@ -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); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java deleted file mode 100644 index 10696143a1..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java +++ /dev/null @@ -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 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 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 jwkResolver) { - NimbusJwtClientAuthenticationParametersConverter 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 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 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> addedParametersConverter = mock(); - MultiValueMap 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> parametersConverter = mock(); - MultiValueMap 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 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> 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, 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)); - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java index 9dbcbd5a5c..cc2110e35c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java @@ -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"); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java index e2ef5a8bb1..d943ac6cf5 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java @@ -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 - } - } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManagerTests.java index 756eef601c..9c53db57df 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManagerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManagerTests.java @@ -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 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() { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java index 46a7c102fb..f6727b3c92 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java @@ -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 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() { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolverTests.java index f28d02e88a..a4215833d3 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolverTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolverTests.java @@ -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 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 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) { - } - } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java index f847d2b5ec..ef76b09795 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java @@ -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 refreshTokenTokenResponseClient; - @Mock - private ReactiveOAuth2AccessTokenResponseClient passwordTokenResponseClient; - @Mock private ReactiveOAuth2AccessTokenResponseClient 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 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 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()); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java index f455ab236d..59289e32ea 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java @@ -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 refreshTokenTokenResponseClient; - @Mock - private OAuth2AccessTokenResponseClient passwordTokenResponseClient; - @Mock private OAuth2AccessTokenResponseClient 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 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 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") diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java index 912af07729..a7b5e6c43f 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/AuthorizationGrantType.java @@ -27,9 +27,9 @@ import org.springframework.util.Assert; * access token. * *

    - * 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 OAuth 2.0 Security Best - * Current Practice. - */ - @Deprecated(since = "5.8", forRemoval = true) - public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password"); - /** * @since 5.5 */ diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java index 663bb671a7..c9acb0e271 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java @@ -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. */ diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java index dbde6c715c..58b37411be 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/AuthorizationGrantTypeTests.java @@ -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()) diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index 04ad67b462..dc184a68ba 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -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"); } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index 29fe282fa2..1b2c93d467 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -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"); }