From 8a5f29fd74f0d34cd3de6ae21c82b8ac93cbcc86 Mon Sep 17 00:00:00 2001 From: Valentin Aitken Date: Tue, 11 Aug 2026 01:09:33 +0300 Subject: [PATCH] Upgrade to springboot 7.1.0 --- build.gradle | 2 +- docs/modules/ROOT/examples/samples | 1 - docs/spring-authorization-server-docs.gradle | 8 +- .../java/sample/extgrant/SecurityConfig.java | 4 +- .../sample/gettingstarted/SecurityConfig.java | 6 +- .../JpaOAuth2AuthorizationService.java | 38 +- .../client/JpaRegisteredClientRepository.java | 20 +- .../main/java/sample/pkce/SecurityConfig.java | 4 +- .../convert/BytesToClaimsHolderConverter.java | 22 +- ...ToOAuth2AuthorizationRequestConverter.java | 21 +- ...ePasswordAuthenticationTokenConverter.java | 19 +- .../convert/ClaimsHolderToBytesConverter.java | 23 +- ...2AuthorizationRequestToBytesConverter.java | 20 +- ...rdAuthenticationTokenToBytesConverter.java | 17 +- .../sample/registration/SecurityConfig.java | 4 +- .../sample/sociallogin/SecurityConfig.java | 4 +- .../EnableUserInfoSecurityConfig.java | 6 +- .../jwt/JwtUserInfoMapperSecurityConfig.java | 7 +- .../sample/AuthorizationCodeGrantFlow.java | 7 +- .../sample/DeviceAuthorizationGrantFlow.java | 9 +- .../gettingstarted/SecurityConfigTests.java | 2 +- docs/src/test/java/sample/jpa/JpaTests.java | 6 +- .../multitenancy/MultitenancyTests.java | 6 +- .../test/java/sample/redis/RedisTests.java | 6 +- .../DynamicClientRegistrationTests.java | 4 +- gradle/libs.versions.toml | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 15 +- ...ecurity-oauth2-authorization-server.gradle | 36 - ...ractOAuth2AuthorizationServerMetadata.java | 612 ------- ...moryOAuth2AuthorizationConsentService.java | 108 -- .../InMemoryOAuth2AuthorizationService.java | 243 --- ...JdbcOAuth2AuthorizationConsentService.java | 291 ---- .../JdbcOAuth2AuthorizationService.java | 853 ---------- .../authorization/OAuth2Authorization.java | 575 ------- .../OAuth2AuthorizationCode.java | 44 - .../OAuth2AuthorizationConsent.java | 224 --- .../OAuth2AuthorizationConsentService.java | 55 - .../OAuth2AuthorizationServerMetadata.java | 86 - ...horizationServerMetadataClaimAccessor.java | 224 --- ...AuthorizationServerMetadataClaimNames.java | 158 -- .../OAuth2AuthorizationService.java | 62 - .../OAuth2TokenIntrospection.java | 344 ---- .../server/authorization/OAuth2TokenType.java | 82 - ...ionServerBeanRegistrationAotProcessor.java | 213 --- ...izationCodeRequestAuthenticationToken.java | 136 -- .../ClientSecretAuthenticationProvider.java | 172 -- .../CodeVerifierAuthenticator.java | 177 -- .../authentication/DPoPProofVerifier.java | 71 - ...ClientAssertionAuthenticationProvider.java | 172 -- .../JwtClientAssertionDecoderFactory.java | 218 --- ...Auth2AccessTokenAuthenticationContext.java | 111 -- .../OAuth2AccessTokenAuthenticationToken.java | 150 -- .../OAuth2AuthenticationContext.java | 109 -- .../OAuth2AuthenticationProviderUtils.java | 80 - ...thorizationCodeAuthenticationProvider.java | 358 ---- ...2AuthorizationCodeAuthenticationToken.java | 75 - .../OAuth2AuthorizationCodeGenerator.java | 56 - ...ationCodeRequestAuthenticationContext.java | 153 -- ...ionCodeRequestAuthenticationException.java | 74 - ...tionCodeRequestAuthenticationProvider.java | 508 ------ ...izationCodeRequestAuthenticationToken.java | 93 - ...ionCodeRequestAuthenticationValidator.java | 312 ---- ...orizationConsentAuthenticationContext.java | 172 -- ...rizationConsentAuthenticationProvider.java | 380 ----- ...thorizationConsentAuthenticationToken.java | 135 -- ...AuthorizationGrantAuthenticationToken.java | 95 -- .../OAuth2ClientAuthenticationContext.java | 107 -- .../OAuth2ClientAuthenticationToken.java | 139 -- ...lientCredentialsAuthenticationContext.java | 107 -- ...ientCredentialsAuthenticationProvider.java | 202 --- ...2ClientCredentialsAuthenticationToken.java | 61 - ...entCredentialsAuthenticationValidator.java | 83 - ...rizationConsentAuthenticationProvider.java | 270 --- ...thorizationConsentAuthenticationToken.java | 106 -- ...rizationRequestAuthenticationProvider.java | 281 --- ...thorizationRequestAuthenticationToken.java | 154 -- ...Auth2DeviceCodeAuthenticationProvider.java | 270 --- .../OAuth2DeviceCodeAuthenticationToken.java | 60 - ...iceVerificationAuthenticationProvider.java | 219 --- ...DeviceVerificationAuthenticationToken.java | 122 -- ...rizationRequestAuthenticationProvider.java | 184 -- ...thorizationRequestAuthenticationToken.java | 109 -- .../OAuth2PushedAuthorizationRequestUri.java | 86 - ...th2RefreshTokenAuthenticationProvider.java | 336 ---- ...OAuth2RefreshTokenAuthenticationToken.java | 74 - .../OAuth2TokenExchangeActor.java | 71 - ...h2TokenExchangeAuthenticationProvider.java | 333 ---- ...Auth2TokenExchangeAuthenticationToken.java | 153 -- ...nExchangeCompositeAuthenticationToken.java | 85 - ...enIntrospectionAuthenticationProvider.java | 193 --- ...TokenIntrospectionAuthenticationToken.java | 141 -- ...TokenRevocationAuthenticationProvider.java | 99 -- ...th2TokenRevocationAuthenticationToken.java | 107 -- .../authentication/OidcPrompt.java | 38 - .../PublicClientAuthenticationProvider.java | 120 -- ...ientCertificateAuthenticationProvider.java | 190 --- .../X509SelfSignedCertificateVerifier.java | 223 --- .../InMemoryRegisteredClientRepository.java | 118 -- .../JdbcRegisteredClientRepository.java | 436 ----- .../client/RegisteredClient.java | 638 ------- .../client/RegisteredClientRepository.java | 59 - ...Auth2AuthorizationServerConfiguration.java | 90 - .../RegisterMissingBeanPostProcessor.java | 75 - .../configurers/AbstractOAuth2Configurer.java | 50 - .../AuthorizationServerContextFilter.java | 155 -- .../DefaultOAuth2TokenCustomizers.java | 147 -- ...OAuth2AuthorizationEndpointConfigurer.java | 327 ---- .../OAuth2AuthorizationServerConfigurer.java | 506 ------ ...ationServerMetadataEndpointConfigurer.java | 119 -- .../OAuth2ClientAuthenticationConfigurer.java | 288 ---- .../configurers/OAuth2ConfigurerUtils.java | 246 --- ...DeviceAuthorizationEndpointConfigurer.java | 273 --- ...2DeviceVerificationEndpointConfigurer.java | 325 ---- ...uthorizationRequestEndpointConfigurer.java | 266 --- .../OAuth2TokenEndpointConfigurer.java | 280 --- ...2TokenIntrospectionEndpointConfigurer.java | 251 --- ...uth2TokenRevocationEndpointConfigurer.java | 250 --- ...cClientRegistrationEndpointConfigurer.java | 274 --- .../web/configurers/OidcConfigurer.java | 168 -- .../OidcLogoutEndpointConfigurer.java | 237 --- ...oviderConfigurationEndpointConfigurer.java | 117 -- .../OidcUserInfoEndpointConfigurer.java | 286 ---- .../context/AuthorizationServerContext.java | 62 - .../AuthorizationServerContextHolder.java | 61 - .../server/authorization/context/Context.java | 62 - .../http/converter/HttpMessageConverters.java | 64 - ...ionServerMetadataHttpMessageConverter.java | 191 --- ...okenIntrospectionHttpMessageConverter.java | 221 --- .../authorization/jackson2/DurationMixin.java | 49 - .../authorization/jackson2/HashSetMixin.java | 38 - .../authorization/jackson2/JsonNodeUtils.java | 64 - .../jackson2/JwsAlgorithmMixin.java | 35 - ...Auth2AuthorizationRequestDeserializer.java | 82 - .../OAuth2AuthorizationRequestMixin.java | 41 - ...uth2AuthorizationServerJackson2Module.java | 96 -- .../OAuth2TokenExchangeActorMixin.java | 46 - ...angeCompositeAuthenticationTokenMixin.java | 49 - .../jackson2/OAuth2TokenFormatMixin.java | 41 - .../jackson2/StringArrayMixin.java | 35 - .../jackson2/UnmodifiableMapDeserializer.java | 53 - .../jackson2/UnmodifiableMapMixin.java | 43 - .../oidc/OidcClientMetadataClaimAccessor.java | 207 --- .../oidc/OidcClientMetadataClaimNames.java | 142 -- .../oidc/OidcClientRegistration.java | 467 ----- .../oidc/OidcProviderConfiguration.java | 214 --- .../OidcProviderMetadataClaimAccessor.java | 83 - .../oidc/OidcProviderMetadataClaimNames.java | 64 - .../OidcAuthenticationProviderUtils.java | 56 - ...ntConfigurationAuthenticationProvider.java | 192 --- ...entRegistrationAuthenticationProvider.java | 406 ----- ...ClientRegistrationAuthenticationToken.java | 112 -- .../OidcLogoutAuthenticationContext.java | 108 -- .../OidcLogoutAuthenticationProvider.java | 234 --- .../OidcLogoutAuthenticationToken.java | 194 --- .../OidcLogoutAuthenticationValidator.java | 72 - .../OidcUserInfoAuthenticationContext.java | 128 -- .../OidcUserInfoAuthenticationProvider.java | 227 --- .../OidcUserInfoAuthenticationToken.java | 90 - ...RegistrationRegisteredClientConverter.java | 136 -- ...ClientOidcClientRegistrationConverter.java | 102 -- .../http/converter/HttpMessageConverters.java | 64 - ...lientRegistrationHttpMessageConverter.java | 233 --- ...iderConfigurationHttpMessageConverter.java | 176 -- .../OidcUserInfoHttpMessageConverter.java | 183 -- .../OidcClientRegistrationEndpointFilter.java | 245 --- .../oidc/web/OidcLogoutEndpointFilter.java | 178 -- ...dcProviderConfigurationEndpointFilter.java | 176 -- .../oidc/web/OidcUserInfoEndpointFilter.java | 211 --- .../authentication/OAuth2EndpointUtils.java | 67 - ...ntRegistrationAuthenticationConverter.java | 81 - .../OidcLogoutAuthenticationConverter.java | 101 -- ...idcLogoutAuthenticationSuccessHandler.java | 124 -- .../settings/AbstractSettings.java | 139 -- .../settings/AuthorizationServerSettings.java | 373 ---- .../settings/ClientSettings.java | 200 --- .../settings/ConfigurationSettingNames.java | 247 --- .../settings/OAuth2TokenFormat.java | 83 - .../authorization/settings/TokenSettings.java | 270 --- .../token/DefaultOAuth2TokenContext.java | 80 - .../token/DelegatingOAuth2TokenGenerator.java | 78 - .../token/JwtEncodingContext.java | 112 -- .../authorization/token/JwtGenerator.java | 210 --- .../token/OAuth2AccessTokenGenerator.java | 159 -- .../token/OAuth2RefreshTokenGenerator.java | 69 - .../token/OAuth2TokenClaimAccessor.java | 102 -- .../token/OAuth2TokenClaimNames.java | 76 - .../token/OAuth2TokenClaimsContext.java | 94 - .../token/OAuth2TokenClaimsSet.java | 188 -- .../token/OAuth2TokenContext.java | 264 --- .../token/OAuth2TokenCustomizer.java | 36 - .../token/OAuth2TokenGenerator.java | 54 - .../authorization/web/DefaultConsentPage.java | 163 -- .../web/HttpMessageConverters.java | 63 - .../web/NimbusJwkSetEndpointFilter.java | 106 -- .../OAuth2AuthorizationEndpointFilter.java | 388 ----- ...orizationServerMetadataEndpointFilter.java | 171 -- .../web/OAuth2ClientAuthenticationFilter.java | 247 --- ...uth2DeviceAuthorizationEndpointFilter.java | 256 --- ...Auth2DeviceVerificationEndpointFilter.java | 307 ---- ...hedAuthorizationRequestEndpointFilter.java | 224 --- .../web/OAuth2TokenEndpointFilter.java | 235 --- ...Auth2TokenIntrospectionEndpointFilter.java | 179 -- .../OAuth2TokenRevocationEndpointFilter.java | 192 --- ...entSecretBasicAuthenticationConverter.java | 95 -- ...ientSecretPostAuthenticationConverter.java | 82 - ...lientAssertionAuthenticationConverter.java | 89 - ...nResponseAuthenticationSuccessHandler.java | 126 -- ...horizationCodeAuthenticationConverter.java | 92 - ...ionCodeRequestAuthenticationConverter.java | 217 --- ...izationConsentAuthenticationConverter.java | 120 -- ...entCredentialsAuthenticationConverter.java | 89 - ...izationConsentAuthenticationConverter.java | 108 -- ...izationRequestAuthenticationConverter.java | 81 - ...uth2DeviceCodeAuthenticationConverter.java | 83 - ...ceVerificationAuthenticationConverter.java | 89 - .../authentication/OAuth2EndpointUtils.java | 142 -- ...uth2ErrorAuthenticationFailureHandler.java | 82 - ...h2RefreshTokenAuthenticationConverter.java | 98 -- ...2TokenExchangeAuthenticationConverter.java | 212 --- ...nIntrospectionAuthenticationConverter.java | 84 - ...okenRevocationAuthenticationConverter.java | 73 - .../PublicClientAuthenticationConverter.java | 81 - ...entCertificateAuthenticationConverter.java | 80 - .../resources/META-INF/spring/aot.factories | 2 - .../oauth2-registered-client-schema.sql | 16 - .../oauth2-authorization-consent-schema.sql | 6 - .../oauth2-authorization-schema.sql | 41 - .../security/oauth2/jose/TestJwks.java | 113 -- .../security/oauth2/jose/TestKeys.java | 148 -- .../security/oauth2/jwt/TestJwsHeaders.java | 60 - .../oauth2/jwt/TestJwtClaimsSets.java | 48 - ...Auth2AuthorizationConsentServiceTests.java | 150 -- ...MemoryOAuth2AuthorizationServiceTests.java | 336 ---- ...Auth2AuthorizationConsentServiceTests.java | 369 ---- .../JdbcOAuth2AuthorizationServiceTests.java | 875 ---------- .../OAuth2AuthorizationConsentTests.java | 105 -- ...Auth2AuthorizationServerMetadataTests.java | 640 ------- .../OAuth2AuthorizationTests.java | 141 -- .../TestOAuth2Authorizations.java | 129 -- ...rverBeanRegistrationAotProcessorTests.java | 113 -- ...ientSecretAuthenticationProviderTests.java | 388 ----- ...tAssertionAuthenticationProviderTests.java | 407 ----- ...JwtClientAssertionDecoderFactoryTests.java | 114 -- ...AccessTokenAuthenticationContextTests.java | 79 - ...h2AccessTokenAuthenticationTokenTests.java | 98 -- ...zationCodeAuthenticationProviderTests.java | 838 --------- ...orizationCodeAuthenticationTokenTests.java | 90 - ...odeRequestAuthenticationProviderTests.java | 817 --------- ...onCodeRequestAuthenticationTokenTests.java | 130 -- ...tionConsentAuthenticationContextTests.java | 122 -- ...ionConsentAuthenticationProviderTests.java | 536 ------ .../OAuth2ClientAuthenticationTokenTests.java | 83 - ...redentialsAuthenticationProviderTests.java | 396 ----- ...ntCredentialsAuthenticationTokenTests.java | 83 - ...ionConsentAuthenticationProviderTests.java | 468 ----- ...ionRequestAuthenticationProviderTests.java | 370 ---- ...DeviceCodeAuthenticationProviderTests.java | 499 ------ ...rificationAuthenticationProviderTests.java | 420 ----- ...ionRequestAuthenticationProviderTests.java | 422 ----- ...freshTokenAuthenticationProviderTests.java | 690 -------- ...2RefreshTokenAuthenticationTokenTests.java | 81 - .../OAuth2TokenExchangeActorTests.java | 48 - ...enExchangeAuthenticationProviderTests.java | 788 --------- ...TokenExchangeAuthenticationTokenTests.java | 144 -- ...angeCompositeAuthenticationTokenTests.java | 66 - ...rospectionAuthenticationProviderTests.java | 304 ---- ...IntrospectionAuthenticationTokenTests.java | 115 -- ...RevocationAuthenticationProviderTests.java | 193 --- ...kenRevocationAuthenticationTokenTests.java | 103 -- ...blicClientAuthenticationProviderTests.java | 305 ---- ...ertificateAuthenticationProviderTests.java | 515 ------ ...MemoryRegisteredClientRepositoryTests.java | 204 --- .../JdbcRegisteredClientRepositoryTests.java | 461 ----- .../client/RegisteredClientTests.java | 440 ----- .../client/TestRegisteredClients.java | 75 - ...AuthorizationServerConfigurationTests.java | 45 - ...RegisterMissingBeanPostProcessorTests.java | 112 -- ...AuthorizationServerContextFilterTests.java | 137 -- .../DefaultOAuth2TokenCustomizersTests.java | 242 --- .../web/configurers/JwkSetTests.java | 199 --- .../OAuth2AuthorizationCodeGrantTests.java | 1509 ----------------- ...Auth2AuthorizationServerMetadataTests.java | 229 --- .../OAuth2ClientCredentialsGrantTests.java | 688 -------- .../OAuth2DeviceCodeGrantTests.java | 695 -------- .../OAuth2RefreshTokenGrantTests.java | 645 ------- .../OAuth2TokenExchangeGrantTests.java | 471 ----- .../OAuth2TokenIntrospectionTests.java | 609 ------- .../OAuth2TokenRevocationTests.java | 412 ----- .../OidcClientRegistrationTests.java | 918 ---------- .../OidcProviderConfigurationTests.java | 410 ----- .../annotation/web/configurers/OidcTests.java | 787 --------- .../web/configurers/OidcUserInfoTests.java | 520 ------ .../TestAuthorizationServerContext.java | 48 - ...rverMetadataHttpMessageConverterTests.java | 246 --- ...ntrospectionHttpMessageConverterTests.java | 174 -- ...uthorizationServerJackson2ModuleTests.java | 87 - .../TestingAuthenticationTokenMixin.java | 48 - .../oidc/OidcClientRegistrationTests.java | 440 ----- .../oidc/OidcProviderConfigurationTests.java | 525 ------ ...figurationAuthenticationProviderTests.java | 420 ----- ...gistrationAuthenticationProviderTests.java | 791 --------- ...tRegistrationAuthenticationTokenTests.java | 92 - ...OidcLogoutAuthenticationProviderTests.java | 589 ------- .../OidcLogoutAuthenticationTokenTests.java | 116 -- ...dcUserInfoAuthenticationProviderTests.java | 286 ---- .../OidcUserInfoAuthenticationTokenTests.java | 61 - ...RegistrationHttpMessageConverterTests.java | 278 --- ...onfigurationHttpMessageConverterTests.java | 230 --- ...OidcUserInfoHttpMessageConverterTests.java | 230 --- ...ClientRegistrationEndpointFilterTests.java | 602 ------- .../web/OidcLogoutEndpointFilterTests.java | 348 ---- ...viderConfigurationEndpointFilterTests.java | 184 -- .../web/OidcUserInfoEndpointFilterTests.java | 344 ---- ...goutAuthenticationSuccessHandlerTests.java | 94 - .../AuthorizationServerSettingsTests.java | 198 --- .../settings/ClientSettingsTests.java | 85 - .../settings/TokenSettingsTests.java | 171 -- .../authorization/test/SpringTestContext.java | 146 -- .../test/SpringTestContextExtension.java | 54 - .../DelegatingOAuth2TokenGeneratorTests.java | 86 - .../token/JwtEncodingContextTests.java | 113 -- .../token/JwtGeneratorTests.java | 360 ---- .../OAuth2AccessTokenGeneratorTests.java | 197 --- .../OAuth2RefreshTokenGeneratorTests.java | 70 - .../token/OAuth2TokenClaimsContextTests.java | 116 -- .../token/OAuth2TokenClaimsSetTests.java | 96 -- .../util/TestX509Certificates.java | 74 - .../util/X509CertificateUtils.java | 164 -- .../web/NimbusJwkSetEndpointFilterTests.java | 164 -- ...Auth2AuthorizationEndpointFilterTests.java | 767 --------- ...tionServerMetadataEndpointFilterTests.java | 174 -- ...OAuth2ClientAuthenticationFilterTests.java | 286 ---- ...eviceAuthorizationEndpointFilterTests.java | 469 ----- ...DeviceVerificationEndpointFilterTests.java | 492 ------ ...thorizationRequestEndpointFilterTests.java | 491 ------ .../web/OAuth2TokenEndpointFilterTests.java | 763 --------- ...TokenIntrospectionEndpointFilterTests.java | 385 ----- ...th2TokenRevocationEndpointFilterTests.java | 362 ---- ...cretBasicAuthenticationConverterTests.java | 141 -- ...ecretPostAuthenticationConverterTests.java | 120 -- ...AssertionAuthenticationConverterTests.java | 132 -- ...onseAuthenticationSuccessHandlerTests.java | 158 -- ...onConsentAuthenticationConverterTests.java | 317 ---- ...onRequestAuthenticationConverterTests.java | 124 -- ...eviceCodeAuthenticationConverterTests.java | 140 -- ...ificationAuthenticationConverterTests.java | 206 --- ...rrorAuthenticationFailureHandlerTests.java | 87 - ...nExchangeAuthenticationConverterTests.java | 341 ---- ...licClientAuthenticationConverterTests.java | 105 -- ...rtificateAuthenticationConverterTests.java | 126 -- ...custom-oauth2-registered-client-schema.sql | 16 - ...om-oauth2-authorization-consent-schema.sql | 6 - ...h2-authorization-schema-clob-data-type.sql | 36 - .../custom-oauth2-authorization-schema.sql | 36 - ...samples-default-authorizationserver.gradle | 1 - .../gradle.properties | 2 +- .../samples-demo-authorizationserver.gradle | 7 +- .../DemoAuthorizationServerApplication.java | 2 +- .../DeviceClientAuthenticationProvider.java | 12 +- .../config/AuthorizationServerConfig.java | 8 +- ...moAuthorizationServerApplicationTests.java | 25 +- .../DemoAuthorizationServerConsentTests.java | 25 +- settings.gradle | 2 +- 365 files changed, 185 insertions(+), 70220 deletions(-) delete mode 120000 docs/modules/ROOT/examples/samples delete mode 100644 oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationCode.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadata.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenIntrospection.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenType.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationValidator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepository.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AbstractOAuth2Configurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizers.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContextHolder.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/DurationMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JwsAlgorithmMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenFormatMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StringArrayMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationValidator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OAuth2EndpointUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/OAuth2TokenFormat.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimAccessor.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimNames.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSet.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenCustomizer.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/DefaultConsentPage.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandler.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java delete mode 100644 oauth2-authorization-server/src/main/resources/META-INF/spring/aot.factories delete mode 100644 oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql delete mode 100644 oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql delete mode 100644 oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwsHeaders.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentServiceTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationServiceTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentServiceTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContextTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContextTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepositoryTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepositoryTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfigurationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizersTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2ModuleTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/TestingAuthenticationTokenMixin.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistrationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfigurationTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProviderTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationTokenTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandlerTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettingsTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContext.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContextExtension.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGeneratorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContextTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtGeneratorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContextTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSetTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/TestX509Certificates.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/X509CertificateUtils.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandlerTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverterTests.java delete mode 100644 oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql delete mode 100644 oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-consent-schema.sql delete mode 100644 oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql delete mode 100644 oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql diff --git a/build.gradle b/build.gradle index f87c3a0f8..fcf3bd1f4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "io.spring.convention.root" - id "io.spring.security.release" version "1.0.11" + id "io.spring.security.release" version "1.0.15" } group = "org.springframework.security" diff --git a/docs/modules/ROOT/examples/samples b/docs/modules/ROOT/examples/samples deleted file mode 120000 index 63a8114bd..000000000 --- a/docs/modules/ROOT/examples/samples +++ /dev/null @@ -1 +0,0 @@ -../../../../samples \ No newline at end of file diff --git a/docs/spring-authorization-server-docs.gradle b/docs/spring-authorization-server-docs.gradle index 9e97a534c..5840257a2 100644 --- a/docs/spring-authorization-server-docs.gradle +++ b/docs/spring-authorization-server-docs.gradle @@ -51,10 +51,8 @@ repositories { } dependencies { - implementation(platform("org.springframework.boot:spring-boot-dependencies:4.0.0-M2")) { - exclude group: "org.springframework.security", module: "spring-security-oauth2-authorization-server" - } - implementation platform("org.springframework.security:spring-security-bom:7.0.0-M2") + implementation(platform("org.springframework.boot:spring-boot-dependencies:4.1.0")) + implementation platform("org.springframework.security:spring-security-bom:7.1.0") implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.boot:spring-boot-starter-thymeleaf" implementation "org.springframework.boot:spring-boot-starter-security" @@ -67,10 +65,10 @@ dependencies { } implementation "redis.clients:jedis" implementation "org.springframework:spring-webflux" - implementation project(":spring-security-oauth2-authorization-server") runtimeOnly "com.h2database:h2" testRuntimeOnly "org.junit.platform:junit-platform-launcher" testImplementation "org.springframework.boot:spring-boot-starter-test" + testImplementation "org.springframework.boot:spring-boot-starter-webmvc-test" testImplementation "org.springframework.boot:spring-boot-starter-webclient" testImplementation "org.springframework.security:spring-security-test" testImplementation "com.github.codemonstur:embedded-redis:1.4.3" diff --git a/docs/src/main/java/sample/extgrant/SecurityConfig.java b/docs/src/main/java/sample/extgrant/SecurityConfig.java index 1044363b6..4dab4972d 100644 --- a/docs/src/main/java/sample/extgrant/SecurityConfig.java +++ b/docs/src/main/java/sample/extgrant/SecurityConfig.java @@ -24,6 +24,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; @@ -32,7 +33,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator; @@ -52,7 +52,7 @@ SecurityFilterChain authorizationServerSecurityFilterChain( OAuth2TokenGenerator tokenGenerator) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); http .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) diff --git a/docs/src/main/java/sample/gettingstarted/SecurityConfig.java b/docs/src/main/java/sample/gettingstarted/SecurityConfig.java index b10499786..715ee9307 100644 --- a/docs/src/main/java/sample/gettingstarted/SecurityConfig.java +++ b/docs/src/main/java/sample/gettingstarted/SecurityConfig.java @@ -34,6 +34,8 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -44,8 +46,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @@ -62,7 +62,7 @@ public class SecurityConfig { public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java b/docs/src/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java index 82a1cc5e5..8d18ce481 100644 --- a/docs/src/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java +++ b/docs/src/main/java/sample/jpa/service/authorization/JpaOAuth2AuthorizationService.java @@ -15,20 +15,8 @@ */ package sample.jpa.service.authorization; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import sample.jpa.entity.authorization.Authorization; -import sample.jpa.repository.authorization.AuthorizationRepository; - import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.jackson.SecurityJacksonModules; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2DeviceCode; @@ -44,16 +32,27 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import sample.jpa.entity.authorization.Authorization; +import sample.jpa.repository.authorization.AuthorizationRepository; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.json.JsonMapper; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; @Component public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService { private final AuthorizationRepository authorizationRepository; private final RegisteredClientRepository registeredClientRepository; - private final ObjectMapper objectMapper = new ObjectMapper(); + private final JsonMapper objectMapper; public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationRepository, RegisteredClientRepository registeredClientRepository) { Assert.notNull(authorizationRepository, "authorizationRepository cannot be null"); @@ -62,9 +61,12 @@ public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationReposi this.registeredClientRepository = registeredClientRepository; ClassLoader classLoader = JpaOAuth2AuthorizationService.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); + + List securityModules = SecurityJacksonModules.getModules(classLoader); + objectMapper = JsonMapper.builder() + .addModules(securityModules) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .build(); } @Override diff --git a/docs/src/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java b/docs/src/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java index 487a3480a..2216c2c12 100644 --- a/docs/src/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java +++ b/docs/src/main/java/sample/jpa/service/client/JpaRegisteredClientRepository.java @@ -20,37 +20,39 @@ import java.util.Map; import java.util.Set; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.security.jackson.SecurityJacksonModules; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; import sample.jpa.entity.client.Client; import sample.jpa.repository.client.ClientRepository; -import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.json.JsonMapper; @Component public class JpaRegisteredClientRepository implements RegisteredClientRepository { private final ClientRepository clientRepository; - private final ObjectMapper objectMapper = new ObjectMapper(); + private final JsonMapper objectMapper; public JpaRegisteredClientRepository(ClientRepository clientRepository) { Assert.notNull(clientRepository, "clientRepository cannot be null"); this.clientRepository = clientRepository; ClassLoader classLoader = JpaRegisteredClientRepository.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); + List securityModules = SecurityJacksonModules.getModules(classLoader); + this.objectMapper = JsonMapper.builder() + .addModules(securityModules) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .build(); } @Override diff --git a/docs/src/main/java/sample/pkce/SecurityConfig.java b/docs/src/main/java/sample/pkce/SecurityConfig.java index 3560e22e0..7c174b08c 100644 --- a/docs/src/main/java/sample/pkce/SecurityConfig.java +++ b/docs/src/main/java/sample/pkce/SecurityConfig.java @@ -22,7 +22,7 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; @@ -40,7 +40,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h throws Exception { // @fold:on OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/main/java/sample/redis/convert/BytesToClaimsHolderConverter.java b/docs/src/main/java/sample/redis/convert/BytesToClaimsHolderConverter.java index 328443d2c..8fcf7559c 100644 --- a/docs/src/main/java/sample/redis/convert/BytesToClaimsHolderConverter.java +++ b/docs/src/main/java/sample/redis/convert/BytesToClaimsHolderConverter.java @@ -15,28 +15,28 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.security.jackson.SecurityJacksonModules; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; import sample.redis.entity.OAuth2AuthorizationGrantAuthorization; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; +import tools.jackson.databind.json.JsonMapper; @ReadingConverter public class BytesToClaimsHolderConverter implements Converter { - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public BytesToClaimsHolderConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper - .registerModules(SecurityJackson2Modules.getModules(BytesToClaimsHolderConverter.class.getClassLoader())); - objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - objectMapper.addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(BytesToClaimsHolderConverter.class.getClassLoader())) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class) + .build(); + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class); } diff --git a/docs/src/main/java/sample/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java b/docs/src/main/java/sample/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java index b0f330a71..af1cdb346 100644 --- a/docs/src/main/java/sample/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java +++ b/docs/src/main/java/sample/redis/convert/BytesToOAuth2AuthorizationRequestConverter.java @@ -15,26 +15,25 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.security.jackson.SecurityJacksonModules; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; +import tools.jackson.databind.json.JsonMapper; @ReadingConverter public class BytesToOAuth2AuthorizationRequestConverter implements Converter { - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public BytesToOAuth2AuthorizationRequestConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules( - SecurityJackson2Modules.getModules(BytesToOAuth2AuthorizationRequestConverter.class.getClassLoader())); - objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(BytesToOAuth2AuthorizationRequestConverter.class.getClassLoader())) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .build(); + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); } @Override diff --git a/docs/src/main/java/sample/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java b/docs/src/main/java/sample/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java index 6620aeda1..1f9ee139a 100644 --- a/docs/src/main/java/sample/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java +++ b/docs/src/main/java/sample/redis/convert/BytesToUsernamePasswordAuthenticationTokenConverter.java @@ -15,25 +15,26 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.jackson.SecurityJacksonModules; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; +import tools.jackson.databind.json.JsonMapper; @ReadingConverter public class BytesToUsernamePasswordAuthenticationTokenConverter implements Converter { - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public BytesToUsernamePasswordAuthenticationTokenConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules(SecurityJackson2Modules - .getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .build();; + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); } @Override diff --git a/docs/src/main/java/sample/redis/convert/ClaimsHolderToBytesConverter.java b/docs/src/main/java/sample/redis/convert/ClaimsHolderToBytesConverter.java index c2aa6b5f1..37c29b8a4 100644 --- a/docs/src/main/java/sample/redis/convert/ClaimsHolderToBytesConverter.java +++ b/docs/src/main/java/sample/redis/convert/ClaimsHolderToBytesConverter.java @@ -15,28 +15,27 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.security.jackson.SecurityJacksonModules; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; import sample.redis.entity.OAuth2AuthorizationGrantAuthorization; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; +import tools.jackson.databind.json.JsonMapper; @WritingConverter public class ClaimsHolderToBytesConverter implements Converter { - - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public ClaimsHolderToBytesConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper - .registerModules(SecurityJackson2Modules.getModules(ClaimsHolderToBytesConverter.class.getClassLoader())); - objectMapper.registerModules(new OAuth2AuthorizationServerJackson2Module()); - objectMapper.addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(ClaimsHolderToBytesConverter.class.getClassLoader())) + .addModule(new OAuth2AuthorizationServerJacksonModule()) + .addMixIn(OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class, ClaimsHolderMixin.class) + .build(); + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, OAuth2AuthorizationGrantAuthorization.ClaimsHolder.class); } diff --git a/docs/src/main/java/sample/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java b/docs/src/main/java/sample/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java index 05947d7f9..8dbf78fd4 100644 --- a/docs/src/main/java/sample/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java +++ b/docs/src/main/java/sample/redis/convert/OAuth2AuthorizationRequestToBytesConverter.java @@ -15,26 +15,24 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; +import org.springframework.security.jackson.SecurityJacksonModules; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; +import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule; +import tools.jackson.databind.json.JsonMapper; @WritingConverter public class OAuth2AuthorizationRequestToBytesConverter implements Converter { - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public OAuth2AuthorizationRequestToBytesConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules( - SecurityJackson2Modules.getModules(OAuth2AuthorizationRequestToBytesConverter.class.getClassLoader())); - objectMapper.registerModules(new OAuth2AuthorizationServerJackson2Module()); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(OAuth2AuthorizationRequestToBytesConverter.class.getClassLoader())) + .addModules(new OAuth2AuthorizationServerJacksonModule()).build(); + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, OAuth2AuthorizationRequest.class); } @Override diff --git a/docs/src/main/java/sample/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java b/docs/src/main/java/sample/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java index f332d0889..54918de84 100644 --- a/docs/src/main/java/sample/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java +++ b/docs/src/main/java/sample/redis/convert/UsernamePasswordAuthenticationTokenToBytesConverter.java @@ -15,25 +15,24 @@ */ package sample.redis.convert; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.jackson.SecurityJacksonModules; +import tools.jackson.databind.json.JsonMapper; @WritingConverter public class UsernamePasswordAuthenticationTokenToBytesConverter implements Converter { - private final Jackson2JsonRedisSerializer serializer; + private final JacksonJsonRedisSerializer serializer; public UsernamePasswordAuthenticationTokenToBytesConverter() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModules(SecurityJackson2Modules - .getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())); - this.serializer = new Jackson2JsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); + JsonMapper objectMapper = JsonMapper.builder() + .addModules(SecurityJacksonModules.getModules(BytesToUsernamePasswordAuthenticationTokenConverter.class.getClassLoader())) + .build(); + this.serializer = new JacksonJsonRedisSerializer<>(objectMapper, UsernamePasswordAuthenticationToken.class); } @Override diff --git a/docs/src/main/java/sample/registration/SecurityConfig.java b/docs/src/main/java/sample/registration/SecurityConfig.java index 8de5b0591..b26e18553 100644 --- a/docs/src/main/java/sample/registration/SecurityConfig.java +++ b/docs/src/main/java/sample/registration/SecurityConfig.java @@ -19,7 +19,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.web.SecurityFilterChain; import static sample.registration.CustomClientMetadataConfig.configureCustomClientMetadataConverters; @@ -31,7 +31,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/main/java/sample/sociallogin/SecurityConfig.java b/docs/src/main/java/sample/sociallogin/SecurityConfig.java index e381bdddc..504b175e4 100644 --- a/docs/src/main/java/sample/sociallogin/SecurityConfig.java +++ b/docs/src/main/java/sample/sociallogin/SecurityConfig.java @@ -22,7 +22,7 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; @@ -36,7 +36,7 @@ public class SecurityConfig { public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/main/java/sample/userinfo/EnableUserInfoSecurityConfig.java b/docs/src/main/java/sample/userinfo/EnableUserInfoSecurityConfig.java index 01b8ddc96..ccf8fd5b3 100644 --- a/docs/src/main/java/sample/userinfo/EnableUserInfoSecurityConfig.java +++ b/docs/src/main/java/sample/userinfo/EnableUserInfoSecurityConfig.java @@ -34,6 +34,8 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -44,8 +46,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @@ -61,7 +61,7 @@ public class EnableUserInfoSecurityConfig { @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/main/java/sample/userinfo/jwt/JwtUserInfoMapperSecurityConfig.java b/docs/src/main/java/sample/userinfo/jwt/JwtUserInfoMapperSecurityConfig.java index 25c368afa..f0b4407ff 100644 --- a/docs/src/main/java/sample/userinfo/jwt/JwtUserInfoMapperSecurityConfig.java +++ b/docs/src/main/java/sample/userinfo/jwt/JwtUserInfoMapperSecurityConfig.java @@ -35,6 +35,8 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -46,8 +48,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; @@ -71,9 +71,8 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h return new OidcUserInfo(principal.getToken().getClaims()); }; - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/test/java/sample/AuthorizationCodeGrantFlow.java b/docs/src/test/java/sample/AuthorizationCodeGrantFlow.java index 08617bf24..aa0edf723 100644 --- a/docs/src/test/java/sample/AuthorizationCodeGrantFlow.java +++ b/docs/src/test/java/sample/AuthorizationCodeGrantFlow.java @@ -23,9 +23,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -40,6 +37,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.json.JsonMapper; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -208,7 +207,7 @@ public Map getTokenResponse(RegisteredClient registeredClient, S .andReturn(); // @formatter:on - ObjectMapper objectMapper = new ObjectMapper(); + JsonMapper objectMapper = JsonMapper.builder().build(); String responseJson = mvcResult.getResponse().getContentAsString(); return objectMapper.readValue(responseJson, TOKEN_RESPONSE_TYPE_REFERENCE); } diff --git a/docs/src/test/java/sample/DeviceAuthorizationGrantFlow.java b/docs/src/test/java/sample/DeviceAuthorizationGrantFlow.java index 3abe7dce1..a0940da58 100644 --- a/docs/src/test/java/sample/DeviceAuthorizationGrantFlow.java +++ b/docs/src/test/java/sample/DeviceAuthorizationGrantFlow.java @@ -21,9 +21,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -34,6 +31,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.json.JsonMapper; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -101,7 +100,7 @@ public Map authorize(RegisteredClient registeredClient) throws E .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andReturn(); - ObjectMapper objectMapper = new ObjectMapper(); + JsonMapper objectMapper = new JsonMapper(); String responseJson = mvcResult.getResponse().getContentAsString(); return objectMapper.readValue(responseJson, JSON_RESPONSE_TYPE_REFERENCE); } @@ -181,7 +180,7 @@ public Map getTokenResponse(RegisteredClient registeredClient, S .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andReturn(); - ObjectMapper objectMapper = new ObjectMapper(); + JsonMapper objectMapper = new JsonMapper(); String responseJson = mvcResult.getResponse().getContentAsString(); return objectMapper.readValue(responseJson, JSON_RESPONSE_TYPE_REFERENCE); } diff --git a/docs/src/test/java/sample/gettingstarted/SecurityConfigTests.java b/docs/src/test/java/sample/gettingstarted/SecurityConfigTests.java index 86dc7c6aa..e5360db2e 100644 --- a/docs/src/test/java/sample/gettingstarted/SecurityConfigTests.java +++ b/docs/src/test/java/sample/gettingstarted/SecurityConfigTests.java @@ -20,6 +20,7 @@ import org.assertj.core.api.ObjectAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import sample.AuthorizationCodeGrantFlow; import sample.test.SpringTestContext; import sample.test.SpringTestContextExtension; @@ -43,7 +44,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.StringUtils; diff --git a/docs/src/test/java/sample/jpa/JpaTests.java b/docs/src/test/java/sample/jpa/JpaTests.java index 58048c4d5..1bfd14e4b 100644 --- a/docs/src/test/java/sample/jpa/JpaTests.java +++ b/docs/src/test/java/sample/jpa/JpaTests.java @@ -24,6 +24,8 @@ import org.assertj.core.api.ObjectAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import sample.AuthorizationCodeGrantFlow; import sample.DeviceAuthorizationGrantFlow; import sample.jose.TestJwks; @@ -53,8 +55,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; @@ -197,7 +197,7 @@ static class AuthorizationServerConfig { @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/docs/src/test/java/sample/multitenancy/MultitenancyTests.java b/docs/src/test/java/sample/multitenancy/MultitenancyTests.java index 6f933cd0c..0c9e76041 100644 --- a/docs/src/test/java/sample/multitenancy/MultitenancyTests.java +++ b/docs/src/test/java/sample/multitenancy/MultitenancyTests.java @@ -19,9 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.data.jpa.autoconfigure.JpaRepositoriesAutoConfiguration; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.data.jpa.autoconfigure.DataJpaRepositoriesAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -96,7 +96,7 @@ public void requestWhenTokenRequestForIssuer2WithInvalidClientThenUnauthorized() // @formatter:on } - @EnableAutoConfiguration(exclude = JpaRepositoriesAutoConfiguration.class) + @EnableAutoConfiguration(exclude = DataJpaRepositoriesAutoConfiguration.class) @EnableWebSecurity @ComponentScan static class AuthorizationServerConfig { diff --git a/docs/src/test/java/sample/redis/RedisTests.java b/docs/src/test/java/sample/redis/RedisTests.java index 135f2baf9..ca3d31390 100644 --- a/docs/src/test/java/sample/redis/RedisTests.java +++ b/docs/src/test/java/sample/redis/RedisTests.java @@ -22,6 +22,8 @@ import jakarta.annotation.PreDestroy; import org.assertj.core.api.ObjectAssert; import org.junit.jupiter.api.Test; +import org.springframework.boot.data.jpa.autoconfigure.DataJpaRepositoriesAutoConfiguration; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import redis.embedded.RedisServer; import sample.AuthorizationCodeGrantFlow; import sample.DeviceAuthorizationGrantFlow; @@ -32,9 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.data.jpa.autoconfigure.JpaRepositoriesAutoConfiguration; import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.ComponentScan; @@ -174,7 +174,7 @@ private OAuth2Authorization findAuthorization(String token, String tokenType) { } @EnableWebSecurity - @EnableAutoConfiguration(exclude = {JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) + @EnableAutoConfiguration(exclude = {DataJpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) @ComponentScan static class AuthorizationServerConfig { } diff --git a/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java b/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java index 26692d8a3..bd9f604b9 100644 --- a/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java +++ b/docs/src/test/java/sample/registration/DynamicClientRegistrationTests.java @@ -20,9 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.test.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17b2b6aec..85d334425 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,8 +31,8 @@ io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javafo io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" } io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" } -org-asciidoctor-asciidoctor-gradle-jvm-plugin = "org.asciidoctor:asciidoctor-gradle-jvm:3.3.2" -org-asciidoctor-asciidoctor-gradle-jvm-pdf-plugin = "org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.3.2" +org-asciidoctor-asciidoctor-gradle-jvm-plugin = "org.asciidoctor:asciidoctor-gradle-jvm:5.0.0-alpha.1" +org-asciidoctor-asciidoctor-gradle-jvm-pdf-plugin = "org.asciidoctor:asciidoctor-gradle-jvm-pdf:5.0.0-alpha.1" org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:6.0.0" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39897 zcmXVXQ(&Fl_jDTDwr$(Clg75~)9A!JQDfVUZQFK(#&-Jk{r$h2=jyrIYwejevu2Mh zg3r8x*V`iln5L)ULs?NF_xwq(6LHq&o-*{y)ge>G0d<>gcBCVTVNZ`$&8w!zS_Tqf>a}*%?yt0T&>_t5 zzJseOz`)wgPU@wCfq}Jyf%c^dLCeVU0AkE8pD8)V)|iz6b*aR{vRu6z;$MrL+~dsS zPH02Ikx@zdLim{9@;V9O4^pTRrc=IvO_buB65=pZ7;mjYIN)p9&t z16ISi9lZ0Qh@j)(KEL_bOVz_mlgiOAGZzGwtATT>_&L|#za5rq)QEi&{C8T1Fz2#l zAYoK3fQG&bh8pS@vg7v+=5*}nJ*-s(N85_6CiMe^TXFRvUGV-Fw>2RL6bJ9DZKiqL zSCTiF04)l1PB!6pfe(zH$X^Jl=teVBKR>N{_;#nh-rg@^etc(S?b_Y+Hwr3pbPJSO z19I1$>FbFt=16PkZKcN>I`9JX+ZY)8g~UkC0b2}zJPWRUYJg?a&aRUfP&KyL1YO@# z#E{5JPpo`?);qQ5^e#6t!uxm{Wy6!U&^eMB*k)mFW7WzSV6v#pD5k{#*Oqsley~y6 zDNimBQ`o`**+#h(N+*#NQalvWuydqCH>FeQYdAx#uq5%ITV9!2zHf0WFl23tKVx}t z0BDsXZ*ZZj$nt+HR6T_GKW5%AAkuqJYfG$s{W+&rE2%8g!e+Yig_Oe|STw|2F_rZu zwF|mI4H;5ewdGnZF}6N6N^E%`K{ag&ZJ-zGP1ZM$v~ahle@l`V70bk(^(--=4Mtp` z_d`vf03sj;mKEE_b7ID_OX~3C3Te8+0mgZm4;rH-_l8}xq;_2oaGU)z+l~;@o%O_n zN9HI(v##3^2iFZ`iWF#WqocFhQiqVXoOJl^4aL%yN=kg_jb8VE8I_NjlSdK4FJ%(} zBQ}}^j>S`H)ZksbXB01Wm7o?i;|QQ7sQra`OnwdbCUQV06ZLn zN9nw0n5zd}BwaDm*B*Vd>c}Ss$pPkfY3{VLhXm0KT)6j`4~YNTv&J_hHx3*OY!l*t zmzf?z9N?(3sEQfHOpxMf{sZ=fSv#>?6)r_zdsn#R~NE>!j`ILoIpEA;%FN74%>C>gxJxW7Mn2U9dr159jb zaM0y4@2>~%J@XTEgRp%IX0kDcQ@K%9t)G!t1XLnap|yO>>DIckU?NB_vIXvNZu3T< zq7XCPvYTqXh8f%Y5<7-@I_)HoEx4ATPNX7VULjzqGi|W2Ff*ko>0c0#MD*i6)Z~{i z-flfYhT_qd9nm`Z7h3o_*)rV(GGD@5`t@tkE?w9$q1*k!LqOr8#KV45=eJ@wNSb3E z0wlI5lOam~HVEO~5(Zg?o1R87Y@gB%8q|si_D&~G)tt0)%q7him{NwC7OUe9(GF(J zqEv<)m6zX>AcEQ_h<8t@5){gsu!e>7$FJFAA@4}GoKOru$dnz!{#^8{CRZC>g_! zOmrQ6*>42AaF3vzg_^NP`6~I_&}af_2E46-OH3y-plKnDXlQ@pUQ3`87SMXo2Fvxe_2QyLt?(qb_@ zXNawF*ctad`US7-Ssf>ZiXapkJ+HF?&fsd~xI#4K;#5WP5;LAvJ3yazWSQkM(^oHI ztaS6SZr?9m^Hh5p+bhyFOknsr1K>U}PscAC6wPQ~Mk0E^@?XnAWS9-yqcqc<8sS^d3>VB!i#54S>`Ju=nAEt zLz~cc*?H!rXz5p{>yc6#3-zz83->(tE8iKu=Zmb?Sz|EXggiWAN5^ftj}q;+9_EWm z{<1&zcG}BCRi+tRJcRncvMWiq09d7ax%tEV&z#-N51w_lzJIpxd4!U*2)L#Xkfvb+ zVA(v$W)X%w5lAjpGfU-u=$vd=h&I>*nZoSXxWswF&gYH7aH#M8At^og5=@Q>qT9G_ zM@O4gMu_Q#t9^UsWL4?dS!t5K2V>0UvgMseR%fnD1kOGmw{r0Sq2kAIRh)+BTQzWF*_aOEfSP{$t^r6J#+XntJ|C@ zCE6#3(AQ!OnI~<;9CUCndtSRfn{owWyvcl#UHaB%VOk_!79uYIaEDB>q90LrMLYFr zDxH<3@jZgr^ICGe?UfYPzLrHi=H>S>C9c2NWqD^=mnP&ION=BboX?U~!Lv4?fz=IJX;e;G=*hg9|7xnRi8ue6={iOfdB_C~5vRLi9f~R#KX(^5GXlCo1Nba%MVM2Zo9s)zem-y*Y>cR| zc-NygrZqbNeRN17<_*jI(H*Z@iJa)>#|8wlrN!Y(5F^0SPVuRP9^S~A9C_vF-^NWM zW$B{5qRSVXI^+ql=^Z5A{r|gT4l;WS>JVUHAejHbdLo`8;M|yS87~ML8#+yn{0Lf( zGiw?t&5#ubE4UO~I*u_B8hksWa`5UK_0*Mvl@*zdl3#Cm_d3JMicw(&mt&D3X_B$N z$fHDW>tE6L2EdORt+(uT!?nKBu%Msr$3oY?ed|s>ejh%6!S3;5V|Dqx-u-Mj2`%yq zP%lRBe=-SR1mGf%)L3k=dal;1Cl#ln7HH0ghi_G|18FEAX!wB2@5^D zl~q@fL9CF7EJ7h%+#IuS#X1^gFO>`7D3FPlO{!i4$~V=x~bHV4vS3R>a>L!lz>m)jfK_@RpxW~rTe*o6T#EF!(&4Jjj&M$ZgJgWnZcpW zN*2l{l4Hd~R^?j;+DOgFS!i_pM2io(oJV*|*~xT8`qezy^7ylSzb(07Q5NqC4oZiB z=k)2V7EpV=KC)$tuf1n4xXf8yfV}yk+F;nff*w?{R*~PuHoCSaBjjb<2GhJ;-bak2 zRi4)Q&iSH7;r$H>pL^U;b4iL$B>UwIBs>|S;<+-Z zBS2q6TAUKxJdJ3~+jyw>O0?D$A_9d!Lch`_nT^kds<D1eH#4Zi5ImkyB z_k=XiD#r=vrtd7ezVe`%4ONXp7FZ~4l7-124`=iGeDrL?fP*WW`C8$w{Np`d;XZ=;`^l{oaf#!8 z{fwJ@cJmbyJlx6TOSH#z)kRT&D1bA-a3S-iFhq%g+(PHJkugcKaMB_kA3p~3Nw-u^ z^X?5K^r93{1-;wX$_FAkd=bm@L=B(zdR)2}3W_p(W~+Oin< z_4Ye|`e62&UpOW;mEz8L9~nMy?>y3rCZj`yA6dG?<$zAQsse-pO4yUKRc__dE)s?5 zNwW1V0w1lDiw2%(%YTO#1MV&j`iV&3x0WZYLt5{W_G$vieHijEiD@VzYd&_4cGcu{ zT!b*Ab3}}Leh|f!Cp;o``?H zm?Xbrj_(ORoW8fol5(U?D@peCh@U!4B8WMSPqKHeyGHTh#AZT40;~wF1YoEz=U=N( z%|OFjFBt*#KeP)kc44nZ{I^#7WgufQW;Ih5Wk_6eMxq33O&2h0VPae__h!5AaWlhP zBKLJ|zBLe#!8xu&hJ)vR5j`mAsruIcJQDiYJdt`OoH=szxKrF%({qq`Gz_i#cx#l_ z3aJg0ylr}uW3;JB1zbmEmo{Rn(|@DVuC}$oy9AMq(CB&h)QL-%8ADfo{1h{W`H6Gu zQN7*z!@4nYbc5k5HWS)dVpwoQCewUh)QcfG&mfq-uv$N~LNWR3n! z@9#(@XO$YFX$tW|v4k&2z>Py!QbdjA!1eWB-SJF)^0E~`H$Mt}!3!veni;|g0uxEO zZSsIdf)mer*vkEyz0S_{znV4P1%T-S$8SuXucZ!ib`DnJ6I*%;^&PuQ^@Ddkg@`+Y zIY0Zbk5IyCd?-)NBLFg`%V;agOZOCkCDYxqOAoc?Z(a5sa1xQjn+jcG8})3F&d-7g zglZ94t>{r626U3A@)2E~m>Uz-FT!@EJ{zWIPk}t`vg28ed z0pT;uMOf3|HPSXUW+}N^ep%AU5*9KHb?&<^m2X%Wc?L-aAdhtRc}6+(Bz2~oGxKBf ziK=-Hm9!EEJ89}%GPC##-QNh*Z*6)tkFJ**BiVxi)LH*#T1oB8L0s91@{c6S3q|o_ zo%hrROp&lH=l>ecj;uyO;h&IC2kl7{ffDhkKz$U{0F85H3^B|vAh?18+Jus*rh%CL zMu|a*L6~L;5K;j`G6XQ@A>e@H)oS4tgGzyN!0{r1lrD)u(ig#=>nJu!b>`H_@t$)X zaKdgK^!N1^p^p+9PdG2Tx=gr|ul5>&rf#P4tu|Bju`tj?Z;K-)-U{bF#D$Kr-%xA> z+?%T#K>85aYSH=Ug1OZoNfiZg`WPnj%pR6an5$o7D7Av&?{=~!+8%Zr_pt`yphs9@ zsX3}2ZLvZFtV*0>Y;jW;ucioBFk2XRG*v~+YAF?NY0QEwe)0)Z(h?I#yJnN))sc=I z-DC;F(cVUIp8Fd_dioe}RHz_7mrQN(HB*KHAo1%fZy>*R2bFg5UW?rwG0v#p14icB zWEcMA*NM{T12*oCr2V3OL_#+LMc6SZzDq)ZaK{0JX;XMUDY`U9NKZv*!SwMcpIEqj z-2yAH_Vf?2$dO!(fVA7-{j4SPQ?6JPa|fFvpizK^s4~;s;QZI}7-HC|%l}$l9_oK0=a^a< zU`*w>c&Enu*m#hb?}Uw>sLdoEA|ENQrjND+(K>!*akz4BxV}@aE(L)78=_G{{POLi zJlYl3&KZ{EW8`{TaCWM0Gb<1520s_>CCKxUuLqGjBO9a~+D-Zsx>HR3s6_MBqF}H~ zHp*8bdJa8?qsf%L=x9s39qFmGobeJGV2ORK3taF=McP4RJ2t@hTa%|P&t__k+jB?9 z56T>q&0!OuF@I$_KK!aDiJ+Sfb*>_XY#NoaM1P~jg2Tn6j$se2D5O2&1kaO3rSYzcJX2T?!*t=l9q?Z9{G}?z^Y!B4R@t zZV&KRf_wreC(jjpBU~-%oPECc(Jhw1j9zR6E9o+3

Igj3NXa_|8<`c7r$X{#UWi zML&#!Z(v}7;Q!xk{12s|L4WhH0G{fG&gf#8LK=xBZL-1IORpnrR@N?3F@%SVWhp`>Ml*H*z=tjb4_GvW*iD-oKY+luiJVjwOxsX3nkV25nH$PNA|!E=)yFbC z5Lkj&qh(a3J0cIQ;~H1+aQ%50Z0Q}Y>2fG?c8lO-RUfTy0sagK%tUAm(muuHLfchq z06xkdv_wlWO4)=4&v()zjvHpe0v#BN3;ZWO)4MPNotWQ7tRc!MET%|RfOMQ??qOqc z;;&<)8Kz4`^6%6++KKs4N0paPcy3u`W2?$)ABaPAngDbgQaM-~ZIwip6zR4v;z{F% zjoKc%l$rdTd|r-$9JC1%wamF?kuw$7GyKc%Q#GHGNvIlQ);Xfl#iI;p5mZ6{o27{ha%UXD3KM`Si zJP->nyNM9j?FUaDy9?9}#@F$JuM0@m2++{>+Uc6XxEU@%hMXb@G<&$$j#!L;;C|t1G<`srY*22)koj-`$2Q!z z$dIz=H7}0T*E`m3uLB?Oj33p?Wpc$NoXiUv{9q~L$>#22P2%C5Q9SAZK~*c^W^+6W zF%G=+XB-`{SH~KXd|j%N%)XK=@-f}=LX^n-hXX5L3er^|W2Vq=u%JUGK|p7Qv^fkL%~9WbQm|^ zIZ9c>Lphm<*`Z76qi=8O8!!e2mkXKoi^%4HdEc$7Bae|{GxcL*!mwYGA>Hw}dbota z`-;5EzMp=_xrg5!$(t~p_G!bwh%`jeEJ1O3@a8qLX_u{u!8hEg(lE$42@uAnDn<&r zYDnS>!hc6(b=K|Z_OH!HL8NhvpubpHpg2}^fDVQXZqNuM&yX%vIVZMdmONZ=BE25P z3l@boJf*BE!Tf@$x2!gIr>pm%OpVdQL&u3vMSAybOAWSzP;>W{5M8$%5#JY{029Zk`RmbYE+jY2lg)>JW)3&%0 zP)a}M1ZU`?KW7%cNmoLi!q;ikdgu#Ukc?`<>ytMNxNn+?bL(JrH>Qmb>qe{zza8Hf58}@wBFH43 zRaQn;I>Iw6B5u2~@!LSRY{(!!iKk#RPxQ*(<96*JU(?~rnmrY_H1Wno&fuE$v#dC6 z;~~Ak&OP6%jlIpB?DO0924Pa;b^?ZN8*Mu;dOjvz4!Kt_hE zF(njNfJD1+md1!@4Vzh94h(_F;+Z}>st&^)MAKT!q*}V@xR;6$@Z1!*Qii-P5Q;70 z#TLVTH<&;MJ`HUv7;o1IP5qJXezUPNPs!{_zd+s(1hyDjB;R}sJt{+hqD{r!_84Ea zs`uB4qBtFjFwGX#fh6PXobdnw1bf;LtuZv>*1TrF8JjKy=9%FmuOj+OGO`iw{7K0} z3#~<`duTr>r7NvKRsN!J{aB=Y(16}RZY6wgIo51(t?86Bu)cB4fd9i7rS=k`nq43z<#Qn0Ho%&C2sGtq*U?(X(ZBlB)M?=~xux4mdo?$ge#FIOS-VP~-56r0 zorKkatV0{ky`4r8d|=1F5h91FN4a;6UCl5NE!7;*~R& zP#PEd>m<$~7Dw4Pc;M^}poE;j=7HJbuwVmnf~#d7ugoavQis#bw{uF2M|bLKX2M$0 zWT~}I$C;_NyrrsK$)dz+mEp=N{-b!o9NRh`p2XOC`yIDcl^lS+sS~JHI*>7BD39je zedutlEVa)b7=LCZ9@J2DJJ%S7_lFZ7Dno$!_g&JEwhnOhLb6aC5LRH4M%LkO2%Z`% zxf|1^>p|X@@Dw=5I*Q6U*5^Vy-+e!WrfJ&73)0iBQLa}E1rPe?;j0S$_`Skwi#j#$ z9ce1!DPb9}cV4_XTM^!&HnCd8F>uFdQ@~6)zseSwv6=ECl&w`Gxn$UJ;K9mgGz-Q? zayl#5Gn44~Sd{ZSKu_#}3prI<+lBWPV&)SO3B6~bcZ_qbBd_edR z0%hP0#zX!p%r9KNCCQf5Tpk>7oP=gT1Pe%LR2d1S;Uw^2mTAbf)hOwDrUamhyk(A&#E#znfN7*VP19m%q+>C z2feYOJr5K@&!kiYtFn>m%d$bIqL~(u)%7uFoD)%W*iGhC5k!I@_TMx94+yb&A$-mt z!N3+^|E)79;^;uc{8*seLKHxpmZ1iQHD-_z`dU3Cry6E+3a%6>N%4<`u?TEhY^rbe z)tvGoX-7)BYZs-R1kz<1Ws3l{zdu@eCB(99&$U?iKOwp=W>2NEa=k$&3lDIRRX}1U-0#YM zAbW!b-oP#GY8$I8Yp)vhe)yRq%;|$96E+ei+Tn6~GZ$fBNBJmF4PM>*iF~FsyuE&u zGAxEr-Jk5N%3PXDVjiXmskfJxB_F}e-~Rn|g8w#&6SP0^f)ti{CHh$J8@ehixQHih z>@I4}^aMLCFyW(fl~e(Oa85vwz(g+kBBl_D{_e&;p1wHFAY2jcL<4qSu|rbozUBZM z<9a2wzFPHVv3j=UDMRMgK}WX{mot}wtA57ME|ZJfi-zwc0EeuJke@7AV_BG=NI?jt z6@qf*X*U;{8lKBN{VrBbN=FSah_&+#FhY*g!@`?Wsl58mp*=N6C;Aps_#tzoMCa9qUq*f82yhXr)+iyX@;`vu8__@$6dHgy1^rRw2d!=A9s|**Gf~I&tQ*(&6(k8p} zUa8HgKjIFHJa8K4;etqF8IPJkuCd+{ZIt~fxxn^FCL^m*>l@ne%$G*1u6i?LnQ|A4 z`FG7rfcbuttwPcIYd#D^VFXdq4;b&r3i$?EgHjiVNay8)zf0H7ng+dL}@RC z9>+5j%U+R_>;qt*I%)+;PPjoedr@D&){qyw$=WqJxAWmCq>KOTgP%~oj3>dRGWN^k z0M>e7*EB!&Uc5*2&LmV&b3}>^My@9ya$fQUYXpGTGb{)PLacLbjy?0ZgS{p;OG8{K z)C!$&F0h$43mOUh5$t|7MuyFus}lqe!^P$FL+;n=C*+4smsEfPkdvBb4#6`C-JWA# zRQ3M_PPoqhL`-eSHWC_OdfsB#vXYgy%b;eK{6mK^OB(S}6`{MEZ}v$7PsT{BHP7|j zbqBCNMJ2f;F>j}=f7wf}pCV+%LgaTwKhAj;VHMrAWw!INI%D7V7@_U|25oF+{s@+S zCQE1%8E>(QlDLp%U#mJnr0glJ{^jvNCA{tpf4{F0ppDQZ9?XAAh%nEEHa2D7F~6KQ zkGH`RTDDC-RmqTjcOr2F2+PTf7kduT+QXx920x=6hVtxtgWem` zkPwkR5-Mecoi0S@9sheP?;e0~4;Z1j!k#62J3{VOEn-B9^i>7H(-x4xYFyU#XBFdP zG9&phl5KLD?uEj6cp!X4i)^+3253WlkZ9NlKrWM;X@yFZ!)e)Pgz+)`3}5rJKJbi4 znaQ?VoxErvW1S6evNHn(ITI!rXMQb^Sprt`>eoP+M-3<3g%$OaIR%AMPj-nkpN$|xycEhI5P`89QzOhrj0)5`w+{C zTpEmD9+nW)KZ_5RjTY$NrT$2b2R^@ZPsTWSN7^w*6cQbe%qW&oh+M8aod2igx9gxA z9ycRmCbK&YP3=-xNel$KIns)JtS8{;r}`~ncmHGCVf!FZ$4YlQn$`Dl&-bi3sY_bZ z=0SCLs$PS=2%a3#u(sSS!oP*WJlT{#45z*sLccyP_aL%z+;d_)(}sVW9EMDCp22(w zH;tH+aZunx#A7BLRfb%)O^*H1;inUv0D>J_#j76rMa&wRqc?l?!z0%$s|G-ZFha!& zDSe_Xi|nJiD5U#gEcwQQY|(4_?t|Py{B-n7`C<@E!k=@I(w#L@h*wgIwLIMd8hh?` z(9-I+_6s-VHsX}8OBJB|Zzm@C{Z_+AqUTSOV5EaSd&L3c?<~J4Xs$5RitB+a zO`8SP^D55gAe?a4g^RRSTyCNMnT%3eSB?``6~f-in9419cxLu$L&bZWX;uWc8{WZ= zUn?1wD!Q4BaooG+5Sj2ok!rm#KF#v|cz%UU{PzEbHzP?ozm(r>m_q=nGXh`W9$J}Z z73`#b-@Jva)9xRqRYaX%4l0M4gzj+ye(Sh6_;>=K%u5?BJHqKgU_q?zP?e1Uma^sM zZebitldON0pO&lC@d8{S`D-PNr@I$#Ink_I>zb1~P?PQU0sp6mkeZt4WD#iNCBy~xik1q)E0#TJOAs~Q<%#_(|Ddk zd-w{|?j4$Nn)7Rzt_rSpf3AyGfc$h$>9R6Hn4Sa)!O=2DfTq*g@9!HI63r9kE&*_k z`&6o@C=sUJ20tE_rL9qOC||ROS{pFGC4UiBnGa)rwI!r_qecV5bygdK;CqJ$2>z^b zhUb~>Pk)UT;W>=&j6Qu7TqSktVxKENF3t1!pfLD#Xn4CatOy+-yxrZ}7XMjGot-gJ z397#)v$0e;ypbTZ99wwA0t)Dol+~@`1r7B?@Q$m>IK3Dhx^~5L+;a;YD|Xz4?z?*) zmDL+BIDFut7$*W&N3Op{H#OD*>Tc~X&hZQ;W=%ME-PsH>faQbbhj*MKk^WS>UqUGX z+^Cb`mpw2pW&1*RpgX7>ic{Tt6f^q5YB4?(+S5X3{$ObCH*cs>%*?A-0W0TA8+*Ip z-uhOtb)>OkkQGrz!Vi|S)}#yDH(Te{d1S;rPQD;1syE*7iJJ@$dOKX_G-&*FRB-L^ zI^n*@_m}#3{r%Z;=3TSjJU@(=e#{Epko?D1?YO<*q)=dB+W*H^1`_{)C^C$Lv$?&k zlQn3rgdSk2*6+0VkH0o4JwYQ)1*%uV&1!}S@Ttq>l$!lf@M3Cw&fFN*S&thV%|+Ug zL9ily?KF@u@q{n5CJtDxCMdrIuDg9XGCyC>H@(3UZbkPNA0Ia#Bsi1o#Juo{r0lRS zr7Fi194$%^2u_AmaJ?;L{mk5$1FMgVv3q6$Jpi1bkwqg-D_7=8T^?e|gj~Y0lxF5n zeUYj-)4o~DkNTuFMSb!z$C1=UEdvUiwwTfq{`2UjF<6Bq3z<#`21*}XQG@5y|8 zTq!pCNxv2-IZDwr%AxeBSeR2Qx{X?(W>d+pRZ3Fv+QAHirxDsyqLQiugxX29pmecx z0|AvawYhK}US6{ix)OjNU=YD-%yrZrX7DP`c1o%3=xaN^%bBO-ws3rG9$o%32Y>g# zydnK6ux`og0JA}L*m6x}+t3}64ExU1|X-(THEn?|[sex+d!zoXF=a?l-EdR8AUn8#-&A&o?v{ zuKJSeRZ&*SGgZKmWakNcOXE=)+IuYaY|VM1FQum*(G{3!a)!DqaL`kBPuB38CIxsL zO^w+!4qZ!=`1Ip``2I4j2#{XnlK^Zr(N{#5qq*(vb$?KLRLr;%lE3(stlrywB0#K;?J;QZN=C$I$V z_qAEsJV6esI`qB;jo)xV>o~mVfo=1iMubUx^Vz@e4>bY%TA`nE8EesIb_cviTdJ=; z!4D1)v^&dXZ%raeU_r@GH{sv_1%6{}F@Rjc#2S5+bTkhhWF71(LruY)_sdBFd0Wl~ zzD;LV7P`aH;BM)-x4$x>duJzKZu*QyfKIJXTc)dh6X+$n2U&49>$cGZqn{yPz$A8e z7RQ05>1|Y|i3>JdgONCFK)?<%VhcRkJ6jm&koP{hu@zlh>T5^p^!*7~VI)94M*8!=;6qLq!W_ku8 zc)&TYcqSMR36V|rs*PL$Qz+J`*-u#tiE_^1v@)A&UL7W-8d}K0RA&!XC78rPEYmkA zsdj+s)-$oqgH$!1sR^RF^(*tHOE%do!%B$cX7EhZ- zlfpPL-ZR57DyU2R9wB3-6c%x|w@~VlO31!+$^+XH91})bpi-znd5|-tNe~cxcUt(j z;e~$&6gp`-3s^|ty}@lH1VE`g(TTgh3CleBKTN2g6&Mb!%UzASbpA_hIFP;WA%BK{ z>#&7ni)rT`BK=K68b1zomqPYleWD{99-=VQdH8zL_BOOnNhvKm3G^IP((dzaBqb%t zE&CYvA?mqlo-%il5hVCpK$C%Y?;V_!4Wh>*v_GgC01TlJ0N|S<&n+-`ppN98r>U3P z)Az#JTC)uX@6^13uqYEOqO;0ou~R~fphW!(s{=E%0MUR&r{#*)F;}z&k&w{>q%b1o zh*Jm$)h;Kv+?Cu6>_SQd^p_TSm-KHKWr11my%p(KiGNx7q8Gl$-$WNX&*$=8&O@yN z{^K@yA+OK;wSQ9V&;OAdtB4{XS;2Wx6f=Bwu8FxqAKr1|-IAMbh)XrG8oD7L7On!T zyda;EW+sl~@}=OGl0Xsw+gn(rS%|Jr8HDMQqbdpor_jPRdeZrvdmHfibjI;R<_1!j znD8R(x;sAhiSE*mz-Z|n-zbHuePN-J@ex$5P))EviExk*`29sKukaIKmWa6{t@0-X z--!4+x9dt zBOJB7r7<|_G<<;))ruO~jG{9$Bv^}IwSo#HPt?W1K07X`6V6r1K?}3X<;S7W&aO|W z35orDqzbK9>UvZ8OC|~6$x2cGFIiNk8vOoP!dk{dCEioFz(?oK7^!TQX&hz#Pr~Vb zcAl5;R<~$P6{)(C24-#QDXCzAzbKrAc%yrPReA7se-7|#(@P1!Q9b5oh@WAoS!%eQ*cfO7>5Q0PJp}0yC-WzsG5`l64T^l*}5L ze=7gv=3a~R1VP+DqF@Re8g$JF4_Oz_uE!q^>Z%-wRaZ4~LqTb*7gvukb{&R{|H3Rg z!Vt0yygDgKK%vp}As+I~P0^$Ql#ZJOeLVbNpYOP61+cTglYOKle@dIVoBzydXs<`` zGV+7VLqY+5XFrzuXn-o2Bi3>+dkv@4Eii5uoF~NS2XjA1#fuPT{;h>r5@k)WDP?(m|5qB~|0|7wgeqXx8AAs13)=tJtrv)w z5-18utqS;(~@;%SXT9ivdY zPzWpPZ}DF-l{uctx~9VN&V?=>=boR}-OtlqKChD;VAl0xH=m&}49j(KTux4~v+UT2 zzZ|DfvNO#2=k^&t^KaVV&q$;0@A`BF^vzLHS}FQ_+vt;A(6ub~RB zUtC|1DavJ8%uGBdPPz{H=1&kK5J=?8+a?m}F?BoU@7#tbYBIxQg@5ke(e>(Z&iU_n z3-?(|Ew*kw0^a-L zlh6_tgukzOB4c524(l}BD5`BLxzLH-bLpUtGtvX_CwS#3!-o^{l@*o<2X#YSKI8*m zCjvDdjzU#k)Ask?YZw}E_zWBDKM4niuC|;!!2uk6@6CF7w&iBQO}RuvzC1*R`0pKp=+jif+w)&+3|xwK7} z>@R@e0_L|7`5FP0hUX#dk#+2+k`~rm%A^2I>Rad!8t7A)o@s01&maCJc@5Wmdf%Ds zm4v259!A;PZw>!kA;%IaT5~-+m2uSxN~gMD@cow2l;;coxXo@1#n|Lc&H3kd%J7vr z98Nisf51ts21zw*`9*IyzgEa3uu<;&v!EJ2wNTm{zC-*}53(aL7A)YWaYbaBQcy$p zN7ljU3`-s2woUOuVOCqfknIiW8dV(rP_WRXJ$o24UW#F%<5eUQkhE0B@XHD4=$5Nf z2mDV880{UqDg1l!f&O0@$`Sws*H43jC-;%TS#m6>iB5+v7e!+cCEYSR48?1a=xp4s z`K6zT`$;DJa>{|vq=s<_qW+@2WyD$3#;{#jUw7|upYfgv_$2}e{DR-3FU2$J_2bI( zE2wjESK|F1D9fYs_-)$*L2B~2=QsatNEVhw`T+J8#?h2Ji_>v<`!OIDK3BMD!chhB zbl9M)FUFN&jBF^?K2%Uj_a}d(G1EFXUYB5kF*2$0j9XY@8i?Y&BawKS+@GE;Lw5L= znIQlV+FTW9E17e+*b)I4#^64lee_+Ql82H7J6ut@jUfK=&S5v(BhNWL^`pxrVDhMk z0c(lFL+JOp;2EsQ_d|faMWjzwE(<)S+|K|7A4JE+&X;88^>I8SLMt1>2ExhS={eJ- zm%>RzGK!As|6-Y|=HMxRy z2dBzLv}Q6D!oJF(qG)A=Sk(h9@8}#MqGCsneFwM1iCFz~j)7Z0N{Tp~-+Y<)s4wtJ z@7ia)@mGlQQz1MAlZRWdcs8TN){DUM?)vj#g)HKySoF7K<3ijqyK#(vj|?T&yr28< znV}RB4XkV`k^$i%V#!dC;^x)W0-bl789`dxfgqrL+m7P%E%bDCT53oZVvRp1atbl=yShsR^?B!+V7j^bof6QG;*Gb)6!DNAm{=q_tq^$RQoK@Wn_<3tikBwff@FjZvG)UUX+2 zR@cp&8;;wDt3sVlhMgsN70)8OsSdB-rulDFcD-kwbMAAlyL#WJ^Lih!$n|D>Cp_K& zFSp&7*VQC*hY@j#g~A3TB-PFp30Y?j>?Hzn92oLha1cI|vUnYiCa$`z61S!lL2EN5 zmv1-uxs7pYZx^bxHP#Yx`=+?|Jk^Vv4Xbu63n<6xbfg!iLLMN8aYQN(peYrHL?1C5 zBZ2j|-JOowW}y_;_$84&DyuuG>1-?jPYaC(^rHGAz8yj1gXDybfZf@8wQ2LV6Y+Gh zqEJCnM8#WTkfgK1+0TNOZHS22wjwns4XFu2e0Ih;nPU@FwU{ii?B*%5$@)v#1+Umx z)Gc-#vD=~4R)SDJ=HHn6LL6h^B|;&-_za{T$-wFV2Z=y-zqBx-WXw6qRNF2+i%knM zhJSO^v<~+s(}tOIn7E7Snz)&PR-EW82F(p69HtgFHh`e9yk-_sqgtqBOEQym zZe^O+(74Y}i)gWr0SvptCV^-ewEkBDR9<);3(lpMU4@ zhOWkaBJUDEd8x9J%BjOoewr_{+nF~1KU`g-?M%zBCXVZ6JAJf+y8Ps$Dmk^=PYYC9R1j~V{s!ZYSt*lblnt#y*XGQZIrBM5JcN`WaC88F@ss%vW+14cz-F&)O7W0 z^$uI+QGFm~6a=z-WD*jZH&O5^rWj@h?2Qh#!mD{us7eSl7gqCW_SE=tjn8yqw&Be6T@Ao!seLr-h; zVWw+K<($6y5y`SMuP<7A*hdf1uQ1h3t(H<2c74o857Nh{2S7xlPoml&sb@OPOa|Em z7KOQI++k`Bw>e3teDnxC3TgC2VtPz6HO^_S*?dH%Pt#+fywgbUaeqA(&!M|8`fS76 zI;4W*aS4ucdIA++upx!^(q~aH=4PtV(_m#A&R*lboMAhKYBj>N`YlD5$n-2d=c8u` zZBPvu6?jjUVNXMsY&t`qlTDw8>eOj^qOikHzwR!5u^2NVRl2adBm&Q)Ihc8yj$AU8kTg8Xw0Mr|yH0VzM`QF7 zfHR1HzGbraMbF(SG zE|iz#XM>OOB7b0xoTAY;kv=A^5H-=y7EM{Ayl=sZFhz-`p?}Sigi*49!+(!{U+nt_ z2=pQ4A1HQC1g)#&{y!1}K0|-ZRCb_4qd$eifDBqKZ#d8)1>-yPXFfVde?F}ffWU?{ ztKUW9isvjzTP|v5m*HX|#9z{1`RIG}*C0sRT@b;ta#_bn_~~yXhkcL!ww&Z&H4%(o z%%xFBcXD>;Fn?GXN~SyR5GVKn{ezU?e=Ih7GqQ(9|BSsyEwpPWbGS#B3W0y6ANuHD z=--faiXA}ksBQ<7Q_{40>ED^qSo|29uGj`>^b?d*wBvsIFLB48(tqQ;c*?X95kHd( ze~#XequvmEqaCd&o(HE04q9d!{`YgleQ;3Kpm5M}O@AD0@S<;~d1<@R%VpvxC@c$x z5sR%^=_Vwnc)5I9b1J5eSYkXYIwWS$w2I(S1CUR1tIYVhL$;Z>S3PWDP*Z6obUpwtl;9kqt z_&VszLVqevT1JnV>A`Ew&&$~7t{C^5t9b=e{S-QxIbt3*+9G;- zAffLv9DPuaL#h#t--aeBJ%wQUR_O@^I#3LoiJf`|I<;3uO!IIqd-cX=8e#89*7)TX z-6O9~+c~9DnYR!X;a0*}DWD%w<&FXxm}f?1eA%WWkGa5*hY+<6@uh=LFI z;$j&u~`noqVa8TzrqFG0Nx4>7Z7`N+s3mz;pI-Ll$}?tQoIcbi}47W zKcL&mxG?L=44Piv>AnJr2tVJUUIE+5B8O6Jph3@wAZr2IFzL11lA~4RAp9RjhokNjCYj}n%AnX`%x{h0LviJ%73SiXfmBm8C~gg&XM-X{Sv|t%;=u4{AEHFsCWDL zIKKx!bd&tF7v)zwx7xORJu)-fEK7>j2hjX=6;ege5L`IemobiI!4I#l_*B*i+Gt)3 zxW@;t5D@C{5BvB8KZx*>_0}V3XiWt0Lxnd_YvGfkm+@9w=wi}S$dAFxOnJHO% z#Lii_NZC|_ca??)(Swk3dFic z>6$!Ue}Pc4Bh446k?Aqogh`0$WonDXc3q&gjK=Hco(r^z=~Y5zJ45qZ6a@6Hrcg_s zZrgum;+3Wsz?S(L{I-&3f@-Nuqku-Mh~A_ojoLKYJONoeE*22}h<{!O3XLnhy&&Kk zs!#&1Y>FK`I7(Za&r@g1dD=EkJ7V3Wc}7Jl)mJkvtvPm8bq&^E43l@x(bU{>eVwPSysl!D4!_VnPG+q3zNS%f zE-EHfw2soyVvQPWX@9XAmr$J=m(p@I2I*~TTt#cu=&Ha^JOL0I(SWO=9362*ocA@? zPW(VKH21DNy?cM{dwV$S;c)pFy+2PMiiIo2>A_f78>fe2Eu-|&QTjxlJ_TbmwcvkD z=suIDCu^VT&(p7+6HRG{WKN6X9fe4bv6ZK*9zmQmRO$tFkD{KKwt7gdw+6EWk=;k zZ(VtFp8g_sv#;#46dR-O=jrcG)Al9a6^nf*4tD4py({#C9gFDi)0HQmpsFR_WhYLa zrbSIFfHFGHQGaCFi6!1V{ZpORJa4ftPd@_XMsLTNi7$wNwf`Z4)M<6)P3Ney_CNFV zdY=9#PnXX|&d~Zu@eZT(X6-~#`T#SgP3ra+13V7^F4R2Ao{J@HE)gULuR4#+VE@a_ z7ionUkVEvE8XIA$71W|&83k^kHaZDd#)ko~r9K7AD1Y!>)URL}1wKUgDOg5C9U>ODe z3708YM!YioduzUk8oPg+Fjqsq7$kgbZMvrLpAkhSLA`7I_$vF}v z{Q7q%Fn=q-7q(;NwaFC?uj%Qm7OMq~zRjLVJOchQZQ$w&$!4<{lBA89=K~p_3Kt1j z(5^`HNGrvHM?oS|3z(KJflZrY1KLSOB-u-3cTp-y@v~r1fm9{wsz$zC7gA0iPQ)Xb zLh!e30j{{f{xT%WS{GcPz)Ms!&ebtNjdEa&>wnG`^$wP0%io~+ioK7@=teMW#8@LA z*W4q@HbIyQ*eKg99AaaTsTSgxrwo5l&%GD8OAf z=YM6CuYT~X^YomdU~`@$v7W+d$YUWnZQ=(=X=f(BA9|HMXL0ad0q*I~^WM`G7DaV- z%TbX4_aY&kp7S0N>-Dk0{33_jaqT%Y-)6AK(F(*xiCG z(HuI*iA%Iq)&o3<)iF*9?nSopd?e5BDt~Nk8Rz4YPmw9!o9Fl8J}r5EKL#Y!v*UaM z)E^qEUKYe= z@hFdYlZ%`{(eSuYRz$3(d2UTqA@e#dQ!a5UYIrSNVhvp4Hh9{-;Br6Q;ZZomlYgKd zL-qeQeDa5Io;RtA(J65k+^LrqumRc{`3PI^)Tha(>e?wOjLJJ7R^EwwcPsFtMKGek zj~BsR3jAad>`~y!A{h24_|ZwYyxRL^(qcH|V7OvlfIl6DpPi1DhoTjAo+uK0)Y}xT zu4s-{Yc0_LKVDZc#!tPVYIzy1s2zU!Ik$)+L(StGILP(?3s6f32;H}4Jn#=T-f!%(fSW!?^bX6z`+l``0gTw@-z(WnDwI)6_!|v^N>3(H)w}u$`A^rPKW6iLm zAohHjdQoTwkwc6t@<}GdmjjuY_JHhRk_Cye(U*RILTg6snt_Z>FV8`A|KYl9FjqBx z>HC@QGms3Vc`v7gt``z1FLW6lkxIB0G38}p(pjH$OvslBV!0e$MNz0d^YwaH#lAUUp7FgJB?re2iFM7M} zt?gESv)Ao3+r7R0XH9`>>$jql=FT1o=$p3hgzB2)d%cN$Av#!b&ish0u2`h$}XkPRmAWvrbmeLKR%XRDrqLKZ`HnX0E(dfsSGkQ@AZK z`IH}zU5-{T!6#tx3uaz@#B8 z3cOCB@B>gw2MCINWgXZ7006QAli@}jlhqCnlRZ`re^+v0aByXEO<`$nBtc_zX>V>W zV{Bn_b5&FY00961001?P!A`?442B&FbnL`4L>xdYt6(5iyK#XNLIMfSUh1aV(zHt2 zf`r(E@F*O303HhAg7CqzKmWh&ukVjf0JwmufcNe8K7W-f)En}JTuNQanbb|)T8Eu& zysDdmf0zeygpqhyVN|*zy@`Dt<+4N_64N$CqlaKcwusU^2lHhQW!(9Wda9d1qtUJ zNPYlNO9u!m)M8Ti0000ilMz-OlWSNvf6ZBWd{p(dKWDZ(xful~1Q-?>LzKxfiJ~GV zA_fv5G6~24aoFO`%uO;fGdIo>hznJ#w)R=|wYD|Z`Yg4LRk~&4GS8oy7a>HmF0kqFVEq3ry>z7BzhI^c>*NX6OO5BJRIx6YQGv!;4G{! zuRFhPxi_TtSKMGHW|I9{DjrnVe}p3{Q>7N~sqcv^p@>?)C$9AMsqy-?`fG>r)~1AG z5?PpLUaj;i^${i3Q@^3>YBiXY$i`%eVxMWYXS;8F-=7prG*)e8nlZk*I-(>J63I+u zJ!*1eTuXuoSZvk|8Wo-@gGNFPrsCn`K>b9RMh7|QG?_~2bfz<>hm~k1f759=Xf>2& zNX)cg(h=jkAnv3xna-eDOmnA#l4v$lDaiV?pl(bkCPy@;ChNCs@`2D?a>+D@<}o?) zcO+WCWKC*YHnmPdYX#bwv`D6f+$V)N4}ke=(+Vk8h$`8>_ZCsFu7k)leO5WpEPK>IKdEjY_f?Mm(3v z42Ix8oYsadiTUB)B{M+65BT4m^Ne>E7nBpeGTFP)&9 zF_(5w3$2lTqY~6XR3J@h!V9y zI(07`I0>DaJ%aHKw6T=h=?bQK<4kT!#ggHu+OjvO_8FLdrb|~Vv6z;0ht#ARk0PtM zgF>Z!P?ft|i@USOf4eVN;_mLa7Ig;^AYI61?j>g@mekp43-k!Ur~((cxQHIN7je5{ zF5N*_3O@|Uv{@*8e!j2y2VzNOZyw`25V!efZSIY0dz3Drblq&b1eH!Bls3X_v800( zVfQBLGJK(3iK-3?8Eep+ZAabJO1#oeJqY@`zPJXVlVLSsf2T0q3C52oB9X=u5OaAE zF^f1*F)4RbL`WHBT5@Vcba6DnWS^1b3~_{mIVesSiycI_JI`Z+kucI&G^)fxJ{S}T z2^C?H5|lQ|)K7a5T}mXP?b#CB9n<#2Ht1Rf6-GW7pleG2a~~sU7)FA6kfr__R ziz3^+2l~?be~g@XQPFzfo0=cvH0a_cx><1Z-f6ktkhS=&u!0irNkt+2=7By~?2No) z^)vvI@1ysZ&~0=(n7_tO|AiEMO)9J=?esycG~4Me7&kGHNUBk18$E93w5s&f9;E?wQg^7bU3I8I@mlIVrV5`7AF+^}q7)que&oW)lN*{1a2xKGn(grf{i zB7|*;e?AKVbcG~DsOEFKT8l)~T+Vxx4#@NfeU8cHDGp;qz!zkCh`uPg58ouNvmlSl zw4c7jwCrS|P`OHl35{U(r@FHH5*=b%>zT%J4eZ8=5R;Uf_P`6zxa;(Tw z5JqAV*&lY$h%ssK-z7mSO88cszET%aya%U^i!d- zpD`_c_xKY10vRpKuCQ`b91@=EIR#z{x%eghN~YK8*P!NEnW)O@b46XXoqh|IhGQXh z?}l#p43yXEpf~9ELRWtfzT7&MI{zdp~n6bYSM z!K}{fKassEQ5!@thdVWg6C(aX4tmQdbN@oB&SH3X3WR^>CIX$GrW|IrwLBrys3@PM zK@A;AIF?wi4mdDqp@n{gO-yqpin1ydj)YKs8DkZD?QE0TD%u;H=&E8NU=|gBe+n{< z4lZFCB)Am$BdHmi4n7TS3>GmeosJFxX)&i>2hXIhK{I@Yu63vpMJuT~xJ)-MWB##4 zFij?V^=#1U;MqI}R^qvkQH!-}+1|jx^MrYB&;CeX5z#u7^+e|#ut>MOmnjrY78Ctm{?!RksowFhBu`X=cfk)8!Tz zW*zL})3n_wXlgf-VROrxrY*kBoohEWHTzmRxAu&9d zp|uRTgQ-Lk!?K}Pf46XWw{UoOBzu>HF*?>A?nw#QaBLD>gWJyUM=K7|nz|BN1f#uv zdBGph2Uf;pV~%LZ{2!z>f`vQ9MbBR3Gx+D-BI7qPCYy>PQe-a>TJ-w@lr;V@L>5VV zNzrsOQHO@uAC>tY{us_Qq+lv~)sa1FbyiZvNbfwz_mu!0e-qC9B1p}cMV&1W||XGqFo`SvhZ@L@?54ni_)H8yvAZzP}8t9 zjk+6)8Goz_e{6N|=lJt7S@{byY>Y9iV*K22tY6!$*86lx+SH`dtpvf_fW(g@F+|4~ zn4Zs13|Ty2^lBlaG9@aF#8afyO@%0~0{(BC#*x$GR!!brtwbXJuxL8@ARm(XOPq#E zGE7hWzp~i7yn5Wghn+->skAn`?;h_)+~WFHzwR5ae=B;jK{@#{)4U=_wZ;-jC`#fZ zg_jXyeF>78=&hq&dOz~ifj6zrPR2|2BvkXGjEPtd##NN! zX8j)K;^{10nB^wkYx6VwtRVTE$lKvAJ3o)u2xAeBSfG8%vL7xyz#l_XBu{80T!&n6y zzvDM#{w@C=;w8ijyIyDV!>W?y+#ZQE86+5 z!fwFSN$9s6)3#07J5K&P|3hf!pLPr)`USUwryUVwp!x7@MuZh?YNrCm| z8ozUE^)PMA(DtM2#d>vyt~yF49CSJbXeZ65O7hT3GMQxY1)40Qcr{71LZZdQe-f=6 z1)%ZXL^Mh=aK#oLX9EEcJ58lJHNiZLhy7J}mc^$hLo~?+Awk`8pt>f2FnON6!0FThtu@=3_X^igCmpihh38MJGv>(7@?PdD^R~bH2OT&f0(9M z2FV392?l)4C3U9h=V|&)gLP>10QP^U@7Ia_nJd!t$7KSr9H4(OK+CO`f2;JT*V6P4 zfwTumZ|X>Hfn*s6bxF2yu#Jz?+xO920KcOH+lHugghm5sAC7u~2FM0Gq;}cUY#yXp zf)<{~c$?|X(rzdbP$fFltuE^bTLZ3=&N7xV3{*#&XJC_l4yn`Z9Hg?Gqy`@+o^fHl zyuoT+W-qt9q%^zspE&5Uf0o-VR|!$e?YgWDcAc)hkgm=SkOAYeH-N&>=n+`z`T}+Z z@u3sS)SP7@Rtl6fFA&e?yDWmOMI-b`pgqHG=iO;ue2_h9u7UBahOKF>c*s~m zpBq>v-A~XBUYDkMS;x@mOL!@lTsCvLBm}Wpt`cUpbsD>eglE^3fAR7RHx6@CgH;?E z@OHYa8E#JV+A?lUv(Gr;I63g@vJLYU9WG12xesgLtK%SdxbU!Tko+!qYg2>Gxex2` zKAq*AmYanG8825^K1Fj}HvP?<<{&5|4GfVw!fK$5dotX6)OfsFJU-4^2hJSkgnoXx z;I;w60LLXYz-PQ=f1DcTy;JPY&{u4rf~DN9A*?QE11t`yA*wFtb;n7v43WhwHXBM@ zc2`MG5BdsX&gv>L7KZsf!bCTZ@GXIMp^ZBbsyS`oVOxgZH%JS;y459E{dV2zcNm6G z^If%R{?H&bj_^G|tVT2kYDf4c`2R;TeD6WNfBgtQ5NPvOe;?$BaMmzC+?nA=YhAXQ zCwPSDi+Rbi)?da?=CUQSnVu8*E?O{3`$;l#p#IY@(SC`JN%S<)ziF97HH$7dXOx^G ztB)c*+Ka*hOn_J7?CF~!ijY1!?s2P(G*iUp8086p-4pkW&m+>eC z^A*jv2v+sFbE&8=`m1~>8o=qo2P{{T=+2MDjACuf)v001i| zli@}jlP*>llBWoh26`zT>*2<`Rq)hx@dS&CH-acAf`Z^>+)lUA?15xkFT|5gdL4gZ zR^YMMI}h&NYZ=~B(sXy+u;n(~FpV>%WvhS~3H{3wmtZ{noSsMIg~cUfpjr8|aB$yymPQ9t>0xyXy#GBv!X5|!@@%rE^l z_zy2%3Y3}jM ze%k2}?mjn>O61lsT~T*}O`dRDZ@h>4OPL&X^_Tjon&$Y(pcRXlPc!7(sZ8_WD2e{zb%|^)ljzNhe{O$Cr*ll3>N@q= zC(X-_AU@I+{uHK>8fYYq>0-J>47CL}cUnW)_Q}Ew>CoUmYNf4Ma+jHtv+bxq z-Xeawl(vg1ZvuZ`GSTay%fus~Z+!)0wBgo4&Dc;E6zj>wGPwCmmKk(~kFFH&s-J9= zRBTYLe@=o(1vkD*R*ErQ^v1p-%f~XZ)sokQD$K%u;}hY+4sq>v(qdXs!Asuw5aHlG z8`m~1U(xEJT}UUIC2Pj>nM7{5r3--#QEgpfmnqjFfh&Iob8Bx&#c|%tDy(UrLuDB- z&2CEi=xTz-?p#`eG4@o987Zi$1FiIfH%&ug#%q}7PafYqWTrL`iB$~B7Q;emRL2*C z^0?6{b8km#D4&CJW(;d?sH?Qn<(<=sFK!1TWpbd}UfSoQJv3Zgd@_SUz#;1LHiO=b z%Yh!mA6I|f8E!{!n%%bft)^VT&#qM+UBQs(rqH-UztvtdOU6UM6yrP)a^ccw|MpJ z362irI-SDai*wGUH=6sbImcbEQgnGAz28T7&yG}WF?^(Qieq1-?$ zhYvG!M)~BPff#OWPk!)>&`>6giMinrLdUSIWkt3oJF+#~C30D@_MQYB5b=Nb zARzFBuWiYva*06`;Lx-~)5B9x$E4hO$VZRRqd?w3Cq4P0p$r10iR&`Id`9W&>q(?4LYq%kP*U6A%RYLFzJdT~rgD<`nSzt>aQha02T?6$?#tN&QzJzQ zGdx4z6ZY>TFCDj?^-y!zpdUhG#D{J`06+a$;=+&US;Vht3kQiHTQf1K31fd2%#t|! zAAsfASig=hB8%zt|4{^llF!pmeDt66Q&|}Z*FCr!xCndwxfQ^Efv8(FcU!){U&6}f ze6B1%{R);saxCv;y6_**j^%=&->>N}2U?lOa)96Z=tcS+61Bz^WvaB)byl|i zv>EyL^at^dKjL63Eoji6;L3k{fk|*?e~~o%XZovr+#<7(MSXFw$(71ln7aZ*+-^}F zn0MuEzpS6^*p-oXrI`jDg9DUD8rZ?MwV7+#wxwgWpNZK^a4mKuH)mT0 znDo@T$wsT6YQu#b^6{yB8e(lOy|$;lqoc>xY_VmGC8!U&)~)a`s$bW(ts9zFdAQE9 zc-wGJ!qU7-W&zgX25x^8jl(v+69~u6KwsQa^h4Be11)WdT}61s%Wz-oIxOaDKFi0; z`D_6w@0V|_sK3iyq78WtRdpp}t>SaUHE{i_FWj!XylJ8DH+>DDRl% z<|_ay;uvANy<%*%-w-OE*#FP@=~RFc8HHsA30&)`p^=|=@>O$)81?oH9q43SR`he6 zFGWJ+KFNwm(az>%e7A>DC!E=y&I9)8#~ST|p&>nO;(>o5)Su3TjgjGfts1GfaO!WQ%GkXzHd^gKT$mJ0q8X0qd>JN zKLqfEth9SXG1MG9f)m$}Uh1=?pT0y#B^rLR{3&B|nIaFs^b?Bc znb?2Gzrp0PNe`n63Qqe#Z}7wN_?AYpJ%o~nA7NY|&lVhq)P)`%wcO8sCGzuQf~Ifg z&!8J-jhh?HTzo*h@;d5T(^g{2;6A`d??rQES0zrp_wAK4R2=1oCB z^Cmljownh?U*s?O=ng*0v@%qeG2!{L*3y5zkW_uyuvH<(QX%=-Lds3~0ZNKbaWQZW zX6rrt6dbl0fhOl+ls*1+WhJ+VpPuprGVQ}|xm$i+-0~X})mbUJ9#?Mp_*(*lz76|l z^z-G`w4c8#W!_?b2Dol*E+-H9(6t5XTJIOlq2f_3&3gFzR-7UN9Cb9O5lDXw*G_+A ztx)@%u08la{1l?o#G{^UK9w?$-gf1)v~z;qu?-xq$8Q~^KZrBe#0m)0u^6Q{QP?hk^Di&2)hDh zpxsyaPtyNqXrK?e4M(}TzrQQ%=f8hiLx1!0XZdro%fG{($d<@e=}g&MrcMtXc0V@t zIv=Z|wLA_mcl@}a7%Je7ccVb{D+el8rIZs_ETuc#h-K(7uCFsXzpU28Q9D>i4fEj((rwm>$W+=JY-X!SPO(?_7obbRNfV^Fg6n zb*fjLq82hOp9mJ^CF1mxb#PRt`P2fnNUE#SbW2rxe2GuZS4;KniS>0RQl?*>foO%! zI($e88Jq}{{Su3;s4eNiqZ)r<;dHiH?wv<9Sug1q+Yfhs)q;AS)9TD(ma~3@lK5;I zYB}=tww_WIP&S5bpRHDS)mf@lzu9)C_X0fWlHv5aA0B!8e zdK9V$Pw^)j)MDlSAU5kUugROKxI8ua)f#oa%tsO7>rJHWEZ|XXMHGLdXJMz36^qZ$ zYMs1e4-BUJJZb~N!O9gYJPP$gwIC5-q*Ma>>Y_niq@P{YCb?P_-m0vl>GG-ds$0N- z{BEP)y*iIu9DBW3T_T)YCcS&x!-m=_CO}T#kk0tkr3BV(YCF?Ob<;jMsHpH`s4q|MaMxOy~Zue#@IIPkzo*F^E*XfLoI@R3?)j%9(v-*Sk;F z{wM&kjZZDeH1*ZX>V;)?sR7_7g>u@PD9ZDz-GY~HQ-FmH6RwRL1GrKdTeV$uVGn*3s}QMP}QYFiLeDjh;jPV|KY zZ^&4)N9AG5_H-ZIdjQ)n2s1luiym zO0`Xo(!z&yZuEaj`m<*8hcM5LzP~pV=!`qbH%2X+M(BLB&Wdh&lH4QXFE<>fmC>$I zTJ`uEN2@d7tUW-r4FTu!Xv9^Z)%(d84&uxC+i!I8$oS8~n;IS?T%P?@>--!U(M^uV zo;e#D#^|C=hp98l4WqRAAXpGBocEUVY@5pCc#NWoo}hol=v*9)b~Vhoe3W)T&HYpt z)+?VDK1^4NYCF;HjdmTSS>eZ>_mMa3SUy5dezIJC-xT@st%2p6zy-ArpE2@}!~P0? z>xsr;7-i66yHO>L6}oYZW*D@RIXI5+vfeN+Y9vRPD9Ke-4Ss*8hV_KJ+5$%yQyc+ zLqVr?7LP=66on%}=TWNG>Wq-5VP}Cp({KbfnNPK}3j1A1r)wIKC&Gw>b`}NBD!>P}FCX{fKTnk0t$9J#%d|}_-9oVaAd~SpG z>a#$eh;|;OhQ`OwbF8U7n(9MhC3_6YxLq0Oqr{ z2Ua>p0fYXX`Q*Psu2;$@+k;-Q!;hSMEpR6N}*! zjZFo5Zj64WFWw9LtyB8t_L1>#z?XW%O^4~lXt(s)PWYQqdbvQaz!jZST=6cNRdj#F zzm1Ilx+!YA->djDGJeCRCZVO%$9?|$L-JAP5Y@O2GpJ2(ajvE(A&?1OL`yZTqgGAN zp*5Orz!ekP1rxVvL+_Lm#69oVbb!*D=IK^V&l6)_EoU1f!(2sxcMVL+_cUkX?mLC$mO7q7N)SX%Y`fVA8EOB#5 zs8hm=y$EDECq{Lon&NLh_*ow&+zL7y-u$}d0j~SOioBv;Y;0&e#Ez~*v=G;pjqPX@ z*pDNx;h6=V6K!?fjW9togU*14+4s; zP6u$}2M|yqE_h+YeTY{>Pz|4_!;8P^5U-6GBQ!UNzIN&3m(jN&Vl)QbK|{nfinK%A zQ4%O#C=%hvBGNd-ozaM=D6)S_M0Sr*P0&-6_-2vV-4HaI%H(aHEYJSxEQvluLCgrc z!;PQeUZPE6SDm{$5iHr%8#VMsxnIv8C{4w1v|D+b*6^O2Vk?~-aR=Q`Qjgs`&w0n)wBa!Ci$;ItSgyxj_$>u~ zn;iSwqg}|6L3h&_--8YE>_}wGq+Z1$njZi8K5<#+f7`HtZbRVTN!%abV!(;qj zmfgJ{b7b{f8<3?B%1~J!cO5Azjvd$V2lbBk6!@+hzDMk4Oxj07*jA!6E9k*~@74Ro z#1BufmKcm_7<6W-;g5edjPl2i&>Y-)Q$5S~Qqfvs;kI5wZ{x5G%$F_8L4F>4YU9_+F#AJdT?>byVk@*J*#qLm$)mshT{;bLc@G zaV{Y5@xvJN(BJILHLF}eF{N1?GJKOpLt54;;N(Hw(1}iW&PDdrod)kS_*$pI*Oiu* zOoKl8*k{Ky_XPWO7W+P%-J|257E+3#vR<)|CL}Ku$z|O(zd+_vcTvAa$<;TPy2Yek z`-!NC@iugIKdyJ|XaGvXF4lhTkwE_pbl>$6K^Z02xiGN4)RW}7u%`H%O(#h&M ztT$U;NNjMB!x{qZw>E~y_)`x>yBng1=;UHJ_;e{8)bM{p_zV{tLF+aYxL|WPfqP82 z`EY6I2=;}XKyH4}1`o(18w|8&%iEEqfm(yz`C*av+Pfqixg2W(PW{a@n_p>l);3^; zh^vN=MBIY5BZi~Za}QP3@aH4m5kjR45S~RS@T>w5KHIs4-+e0HyVGyX%_^YM_}N$(L3k`nD{ojL(})s zr!_qYw-Nd*J+JAD^gB(hgL%vsE?9We$BLe$rFJu zp^SfZ+!)sQ(pB*kpDjB@!NW+l{Ga=&F^sa}(_{RTs6c`j011u~9Ohuq^)UY`h_06k z{IVQl(0Pde7;!sV4R@=jQP1@RjQz9#;29X@FRe)bueN#{n*v4vr}!JIS{0}Y_zr*2!k`hTLIv#C8de3pLBB46t8mWG2fctj z#$yVnV|{T6xISCHc?4Z!)ZsmV_kh?>H8jdj0LzvF*o-NExS(cY zQzPu%9`Lw7On;Iz|FUdKkQc0?1-dO$`o)xLjxAHoix}GC-bxlPvI&HNd3<6X*^_?} z@CB-1pu=iDeBCC_&Lg7aY&8dZx&)#(LQj{;Ev`v5G^m14zt5llzd2zjwZ0QjKy zNGWnb#~KyJrmYKEAzJCwJ~dnc*hnDosH>u_o)7WjfN|%s#R3}ya*AJciCc6ZrPpgz z^O#!puv%GAt83JG1+`Wqsb{RBrm1b61yozh`u0O94#hn{(YCm|Q`{-;#ogUfpcE;^ zf=h4+q-cR+2bbamFD|86v7)6E{we3))64n3td+G_vVVDJ_GD&f@5#K+1Cpz^ge?Z^ zSH!Q=&B|-Wev1`$n2Hb^rI?0qJb12?^9m|f=-?@J)=`IYR`M&EP{!*m`NLU~sm zzpCXfLhh?475<$U!&z7I(_Qw)lza;eBl^Cx&+(l-;q+qouF0C&Ev4YA_~@- zteWJ>6jyJbI+YDDn3R~6QT9tIS-4bXjba^~waeTT17$us!O3h(Ih;79 zZd0E`4$_#Ex#m-C?_2qj`&d3WD;K_>d#rp-Fwb$!GNCI_wg8Nh%M?y7JJ6s(NXSeq zR~^6+n>KYZ=)&){&|(FMjfqD5PJ@O$jc)uNVgoV_R{t2G^fPkTe~E;7-wc}OWdyAQ z6^2wH&|jb!idpA;JCSnW^u!F2GnQ02Fy~y@c>-E-qEjv|7t$He>Z;SbU1j+pPh<}T z87Tm=kZ=(_h>cod?!Qt#b7)kmz;K~TKM?ZWLxp9)9BWpUr!tv3K3j4cnqS_(1Bn3& zBV#Iozi4N4YMgxTd$Sy~fJfe2`f-<&|+Stf5!sU+L*Zc@On4PkyEI|Hw?aiuMFG8%$J|ONNdi z_>qLgL0~Arb-ONCqT+ynS)AtQf%GAXn{)Ug%Nnbm%qPwP*c&v?eyoHIWP-|d;qRXG zBda4?yx@;;)|yd7if@gedd0S&@2{h!qcDb<`eV_w4sU;)z~ZvI+3xnq3>s( zaVoW0Yv$!Eo7?G8D|9D9tVJunC6U{2;r#Vr>1T~<;;T85?Izbi zpHs;^mJc~g1lz+_ACnX;IFWG57znlxWdtT#*Dq5$^h>~Oi!zDYx#^jDd7);h*)S>Vefb-&kp$SG@S`W4yVR|1Qy8*;W-h3yB zjgpT%*qB~!kmE}yxZuY2NL8w$~d9BZue_mMq z+Qji?!VA=s#c3SzhzH1UY12O`~Ec&0FVLc-g-G8YxFu3M3xklaW2cXA^MB3yO^NbxviKkJ? z@%MuGSuN;sdB;ZF>UNSwnM`?X-H8R8n?TtN5MD%v2rnW{P=cxwvJ_r~86_WApWU@V zlZMTvdY@DwCltLP3QJ0p$k>M|MX?{UlP?k-wJp^efrW4E2f$^Vd2XyAfXQW}DAm*u9D)VL^uy?Sz~cCMi^@SMFT zqYKW;`_KcBbxrQ4VZA0a_>7v&o8kE**S4kEOqAKg8=JVI8l~v>N|w54R#b-;VvBZ8 zuS4FVF2BH#&I_`>;x8a9Ry6-SCm2=XWRH1JG0a`t8S%~gQBGk|*#KhsHs@ptGPzww zU#I^EWzDK6_t4$L#d`Wc2LgT=1V_j^Y%wT)i`&t|2GEE4^=p*QC0)nT)}BnE<^Zjdb( ze3VD#0sH6YRFiwI(CHP<3ruWy*LcC-nQGEb?qlYNnmro$EgE@3bKpGydh%7ZP zduvZQdnap!mo8RL)*k=LRC}jwq(`Ykbn{4qNljY$S+Z7dUmlq>dqW*XKB-Yr7>))8 z$qNoYZXrcfCY}xBJl1-c_$B>3TJSf=TxrLdTHLlD2d+l~mllh*jSR-dne|LOwq|X? zLMzMdx1e7`RRDI&@U?;NNafG2ma>%jcm$oQXURay+l92 z)Y5Irnkbhx^8J-bnZ@eaO+1ipqBz}{Gr{-M?L8l#LAKb6Zu*TAAT zYk+^VTk#01fa1>6ZHsM_4N6fH^#+ix7*bx;sklj7ohIW&pW#flZ*% zYtSb*;SfQEhrxn{>%3RwSBUgA2det2B(%5{Po7L1paF*NQ1lMUrQsoW#OTm2dR2DivWmM|Er!PNx48T_8@bFU5pdQXonS56kA})p|+Z6QTdV1zC~7n zR07&hFJIAqJMd(t>Z(Bgw!o!#L<|4?Tfd*BS&tFY6K|o!wA3fhY{NNp6thRV1X?0O zUH9z&dc<|0um?Pw&nrvn=!tDYC#il)?7L%}D8NY}VR%qOr7vx2I>aqRfI(K^i|TJ|xURK}Yt=?i%klezZF{h<$4fi((?DU<#!K-Z4-R=m`xt_aEuXSZ zxqonNj%(^4k$7>sL|JLs#`#&N|A_?Otpc|9JMifXttm}?|M_dSW1yM%keKXK#cdAC zaZH>jX^Z-*+IRKB!@}>6odq2@o(+W1?fdHY;uUm?#Ndk-Ov!UnWYetmuP!0Q7YV?r zTqZDgwK+@7HS#n1%^Km)NzSUPxGymK38ZxCwmCWQ4mCy#^Q+}ITxrd$=`jwAFDpuVjl=EFq| zbr+*eTZ>JeTL(8M=T+@Ui=EWn9IngBF%kw(T0NLs%j<1)91tcl_WXu%Lo%g1o0KD< z^2dv$m8+q7<~e9TluWeM!hPtgXB-xBdJ3j%PjHq8MYtpJhF~nM3dZfw)B1)z0=y~z zn}#C4h7F805s2z`#u4)T+oSb~K`kybV%wQ98FTD1j}QBLJ(dWa&WsXQQm0%!6HUMU z6J6!Yh(r38&!-M?OXn?oPZG_^0(-|mew_$m&N&HxM&_3U;n;H17}6_3_R7@VpIp*5 zqpRIC=9}cpmoHJN(rNz+Oe_Nq#{BeO`CdKB`57x^FUbGQrD@4r;byz8KxN`>BYoY* zUJ2+bXA*D3=TnCdnG3{yw(Prx(;0*Ckx2}7;vDJ2m{&a$KVODdI<@OA(3>B%fe?Cx z!rhYmzILm@iz>9yM%tU;iNcDd{WP@A{fVK{pMeW9>D&ojfdXz+G+fv)_Q8p8+90Qn zsIXiLvS`tv4!bYGHs;tRr{V$$eT}EJs7!QD%NGyWPJ0a2N%Qkh7C;?Py`4Yi!rtbB{atuM@{%$uL`l$z*T}@wzb=Lgpvx( zS zTahngJp`EUmAsX38nSG&?bU`D4ok+kEUQh^*{$1-Q&)t9s;>8Ud&YKH%GlE%X#qcC zlSq%JQv|6TrFv)G)WIQZvnGN;W*ia{*RzKHrUSF}y%O5J5)-7i8rCc0gSII_FF0~_ zzOz*XsSSKQ`!2)?dM{=qm$M(;#z)QwilKTZTKZ*WHA4;Z_RDG4_z4B-&0QY)_67Zo zB0`qX@b8gElqv`UQT7tw#xVv))C$&EfLn+Q@QhMfJuH$mG6j)ToSx_lF+bsbU!2%_ ztOPr#{Ps|(F+Mem1QV$%xU^!Dsg@Qmjm22(GDU*19dG%YLaNnJ(51EQ%J9#&jh{Bl z!vYdPevdi=g8JRA=W03+M5Mb7J(9Py@>TP#go!X1N1RMS0=)79p78e>>%6Y+;g0;J zz4#+>O!9snQ|wmz<0W-AbD{);wgm=kG}93{qq8phrQYDmX_LNEtT$4G=TRgk^x7We zV*O3UTim=NeQa4l@F3sW=E?)RnD~f;(@?I<4VzA-@04EQJg-$)2#53y2Uly{m4~*P zh!;5espS1YwX5l=4>-CDy64kLcD^9|jM%)Z?VK3ag=oBdR|D(#;k1|fI;D&fl0#-!UrkqT-Hclv)O9EHZ-b}!oJ9R zE!3FI$}oq7nqAdkO09Qr)@w7064SDtf>T(?*y|fp=hZle79Gj4EB2{^iLW-|phq^u zdh5(c`l_$6eSI8B#)OhqWUVDL@QJC=vHEkcmvn?$fIC8wMnWS`Z4UxwBC+V)zpFnV zH>D#6p=dIDXvKU;CabJ8dc7N^%(dA_xF9m8%+>2wPh+CrT|NGlkA&QC-mzYGY3Ai$ z&@NOZPMzY`ccTfovz)A>Q-=6h%$3Bv^%jKP52V0W*H4ef#RYo&!z-;ruqp|MCufYD z#=Hl{b^GfSRt4smr9bntgf3$)dn1F~u5-Zsoi@kZz5x<4NUer$;7hQ3^)!9M&lh|U# zLP0YIT5D14@R+a;oalxcl#Q-S{I1U@>qY$jO;o<JKrt;m%&s`%0xh2fD(O@<5%L_Qbf#GN5GtG1K7Z^MT3dE)fk>@=2l` z3%eW1n$|C?6Fj48%FmvV(}~%1Dxi3l5E8NR=Lb5wpyl|#kW{6tlyh(e;J7FC#))w96j;xT@xQ~P0 zP#w*qM>3WZoW10M>WguQ>%TISMv4koU8gEv)QgNof;f$Js&=LiiAtZd-dfEGo1^yg z7IuVhH1iMuH|3X@QTIjFckOdZ!$1L*E`LR5d;F!271My}^R{lq#Ia?f0OexFTRZ~y zIg$iJEeq@~FJzk@lneZDjw%CwoxY6HcaxeDk@gbeeeGqLmS^z=xVe~qztn^x#$?gW zre0T%@a}Lah}jTCQ|eBGUtKgBTj#DbdbQa(`?!gv3=jruk|oI zu!U!mr9U@1#ZJxctmVTrE-{8#$KjyVxWJ(qtljOM9Lc$SS^k6^tDRrFxP{zc5qM z$%9Wv5=u=tIHZbB8spIMmgr}G>km>1Z1cHAwrO2Nc@dhZgwZ#n3VsBjD!CZHmMCzk zov4s%0G2`^*N#FVn>nfCDLO6wT&7Q@3UA~B>XU&e%85Ur z#+A+j(xG+*ng_VeV@mU&wIm z^XgxEFRqJh=cEi1+{dz{3T<$_0YoA?7F+ z5I?I-_n-_g91#RL-J-`3vA+Rvfy;Ame>fo`2t8PxkP^HK!2>$UuTE$of`HKg0MUD( zJjor{3gHDx2K2R?A^>58YLM7H2%F*#;>hI#I?z@qD0uRRl>kOCLsUrHD}MkC53cY|KpMbhC^t8e=H;b0P%a#`IHbJFaZ8BG5`QL z?g6h=z@bINV9p{w;2$0Kf7j7Z4FMSg|432;0KE62h-e`IYA}^N?OhxGQKbR^XzzhA z9Rx@PE>XH`Y?M1yBQS0W@q-iVyL#l7pn{bV)`-8m^FQi6|Igszu=@{;kBm?~0w3kj z1OMbj0RYVRij1@W56lVtC$k3;k?kH78T1?aJC*A1!SrXY8UT2FkJS$On?(cnl+pnI znPEiqKAL-6eb!yUL{LWHKXYP;g75b}E65$>0A&UKxrqe;*zZN6&HD`z|9@xE?mEDq ztMY#zSVPfWeD*RPpm)T7&r|@w(|hqPq5pxp&_+ z^zX)?GRZ%tYb1;J;3OAR4#zHr@(s9M?_&VOl9K(B;Gl?5vJF3JQ6F-eg;|sIXXC-A zP>sU>+<+b_V4sm=zEQ%rn4Rp7O2w`m(7_{AX0s2IFXuQNsh%&QcOs@dTH0i^Vd43K zf{%*7t06#dSjhOfkd2U~y>_~nHL!bgKf}jd)x@NH*)(wSHqf41WO*_>gh30d&|bt>F@WIMJ!Hpd3rnG zF-Pf?_-RMyerE7qZ#@(;q#&110RSJ-^e-eM^( z-~9Bp-5)NfVCa>}1Gtr>T9jT7#;8j;VG_nDZL1?Ix=vcJmNp0zgvzXyTf`fcwP4eU zyPBj!_XR9UP+F`Z|VD}c-dr)K1* z7R|-HIT42)S&3A|Mw?!o3#es9F=;}BpJ9><89cZ|5f!I5#urDb6Qzaa^}A?N+Eq&y zKAQu}{dRIK!d0?Kt$YJrhzo7s^lbKIT{Vsv6%-VCiBAr4U0 zu2XKtnkqNuZ!~;vMWDl_hf@h#d{~8Oo2?=PYHce_j^IuFbOicjGa!maT^K{}3zALM z_;jQQIIJ~{+n8D5XyHf07HZ0eJziZ%++#tkh|Oh8WOC2nm<@xoJvwA4?h7L@Xyfi(3!0CO zk0Zu{B}M}-1jiER?nTrvfdJEUdVnRf`tmlw=)T<}DqGc{NiHzklp^74r`2!g2g{6n zw@#8$KYLgqyU^L>55^~pYb7HR`wR<|p5})dO+cl!q?a5YpvD|d`-BLhgJ>*yP2fTk z^aVe|vY=cK#7EG*5mNKlInhIR9W+BcR zNN0@@)RZM!O9VS~s>lcz8>0gD?~Tub8!gOL$X+_mv-=Ngo%7Hr#^XMRG-_Nz z8^YSc%nd%~r!&i4BOE$&zwm0kWy7479SWsl$TxJK<6gMS339tbIFu7DFRo!JzQF&} zoBIAauR*d1mIJ_EdBFrD&`1QXg#`O*MKx%kmF?T`00xwXj0CHaA~5pE@TpPrhwd5@ zhZF!98TbfeQ)EVA)SLNS98-QCLn8CCm(>z5^OobD=kBWvcz?f#>i>R(V2T(0wc?c&b<@rW_6z2k+yE@xH_oQ_R2^2L%WvFE$kn89 zL~C=9*Q*NX69{puk^IVjsp-?;R7CT!tVLW8bvz zn9=LUIml7>^mLtKfdXahr_EEQV=E>uE0PQD>tMA--&veuc?<47Dn(O{QZ}Ezbl4U4 zG8abf`U6TX#V8}Z?NYluWv7pO6J~$U{!K@$dqN1|BetZHKY<52J|`nILCjtEp>n#q zLNM|BD2u}D={LYcf#dpF%Gg-jxI37gyplQthl5_E{lm%?ozmxdg=HMQsx4TO)C5uS z-9%VzyB6VkWKPT?uHt5TNXkCgI$eK~VjW5ik^+DMN>l0F9~2yevxIoHufWW_eWMrX zItxsKfjKRL5mw+d<2srW3rAY$%xquciP&!9Y=GPXR${Pv#5hl69-%MLH^C)K$92I& z>uPPXwaG5rEk1~m?nE5W&oIqGl)0+-n$72)U|RPH)2Ha?bU_6rTcTGP!U5f27)Hh@ z0pOk;S%y8&PD=#K%|Gb#EZMt^DG7UXJ21kZNPb^1jAZb#_WTGf*gN^iYiaw43}=kT)(_W4S8bulFF^A86wDenKjG#HOBqrJXWvv+<%Xpsdo2Pc*Rp; zMdeP9PPV<;89Xne)K8eT6dAR_L4^P2oa{;vLpH}JMn%B9xl0B|Yk%wKi1B~rLu_>2 z#s~ohW(f1Y$%_h40buKmZuzf{ve+E?iWu#6Exv<0W^puDv`9ABRLIPeRn~|alx4P^ z_FGU*E9g}Pzx7`2^^&wluSp)Z``?Bb3%XZZc*{05yXfJo!8sxWy+vXf_akrnj z@DXU`+Wg2Uf8u}ng>FZzh8Qj_)}g>reN@zWUFbN+Wz=k&;BnDf^r5oFBC~@~Jr{dF zJ{9BS=5%faEFs_UI$=s`YjJXNjJETq@TiB5A<8DE7~vqor2xzr8uo(N=GqS<*0Ur>Uu$FJd8}P?Vmu2&Mzh2 zE(kI0z4}RdPh8fEXuDEs9g3;YP2Y?r&Q|FKsRLa8QBmdv)(K@OjJ?Ou-F-Xp`?eF zB4{f1nwg_a)*FoYR@6yyYE4v#*7)!nOQCq~DJGKc6Wm}WHGmyQwZT`gF*YqsA2e-v z;C5#DLnAKz9)iQ*nBgRxaLAFhDf*!S-}V@LNOSr?iS3PFweKDuRU}gZFQn zhoTsLB49Cnt;KL8OpF#7i*THaWH@Lw(IYB&hUBMlk>HBTgk%QKROqa^P+=iQeo447 zy5J;zM}(VRWT>*XizTMCC4%L1aBpUbW#g$6M)S**0#r8+i05GRM-N&9*M3tUX^ks zPk-<>0vDQMKi-bF@}0^%XT-F@uDfvqR|0zvA-iklr}P}dB~95OU2SJReHAYuI_X8> zP_&YUV_P_}OC&k8taoelYLCBKUMl(SYv z1{etf_3`05LoBS(MVs&A8f$jlhfZW;!vZ^pj`k+Sk92IxS37~rPx`=EQGk+S3 z*s$cdg^sZ;PfD}}LB9@4aeJY0hIk61qq!Kyq7Un+qJ6UDepDUuQ!QqxVX)$qB!9AtV|i=a3=%@M78ru(IN& z@80MU3{C8fJurMY-eG`RCAM5V@xm57FKCK4j;sa!@(QEo44tZLE!i1pwPL&{9^gvY7Cu4??)tdrnM(SASL;#L^RWSfIOkx0IU*>S z-Df- zGuA2$R}YQ@tMz&ZThwLzjdy>w`ABWy_7-eIL!QL87vNKBs2Y45Y=mEj(?O#!zcw9=l| z1tF*^zMK$Bw(_LH=$FTK#)m@E%e_Jx-jm{fCzrz9DE=c=mE>Q+U|^MgQV^1wP*Aq| zMG)-Uu7&Gxble07FRX^ad;}pd0v|90eN!! zJ`8~Z<>0-ivIoHvAXfU=w7=^kvS!~WiHP0_kwDb`n-OS1n)M4uOM7g*X*lP3$2g|V zKUN#JMcX`dYs=#KoBoe6OO1o~)$#3s_$!6I9@{CO_KH6WI4h45GEWdkk$BJfI)sW3UHBQ|U3Fxk zmfc1MW$hr(PdWdXxBNKg?QZ3X7%a_%#ea1hFy~+1($PU*Qd(nUC0%V>cI8Q!E*-~= zIMW9C0-Be@5CHSL7OUz&9#m`-S#{Uz!nF_Eh9U0E*qqm+`RK7 z@{QdQGQzNCLRyI(ne1*qvhTE5FWC?J4`rs2f)@cuF{InKenGT(JhdkO{-zfQsr@ht zc*+;cxqa8(yexali!6rvTiAUtfr7is^$Z!J3-d;X2FJ335HL=dk&kIRS9#=vQ2khlOlqm|DPnekAtqM` zo!KA}2${M4pXTPNTo)AnEywA}rg?F84Rp!eA!{O9M-T zP=#P1B+NIm0p^2Wf+=F_i3>=iQh^bD1ITvm|MuhmG}eZYqTk5Hd*Qov9I!R%yS@zw zdTHltqfPsN7E3lPV}#<)W9B;hECN^-Prqmp&gL0|;5%Ivt+EP7wi~*83;kBT2ciDr zPPV-30=Xk6?k9GWugoHfn1by%SVGaGzoCe$W5DgPE|Q^T=;34$1ZtpYRNPM@n%l@T zT--wBHFVfpEx+#=lqFx`rVQnzFG8!g_=p6y(ZcaX?z!Y24Mgvft`(n%aRXXvPmZE5 zQ=hBfhUm5YKLjdt1yj$oXF;m?8^d9+q=;GkxG#1TQP2yHB4Vwfu##A6m zwcBKtj*GudQFRjLh<1$aemisgjhYEqNkb({jgeQboLk7sawxAPI_7CqFWH`6rc_9* zgt<3>0mzO7v#=3=qr8K))tpn8 z<@CDS6c!TCrp8XO0xrnQVPi^xaw;Ri@;P5_la0He) z&3gBcuZC@_x!@P-e=b{?nsiQ5a!e+5Qj|0Mzx9H7(qwogd5hW^kRO`VEOHtNQB9OY zm+h#QP}Eu_L9a<7X}eTF^yBVQiA$+xEA=8a0p)}A9bQR>;Q8A-&7x~o)dq^tv+q{6 zZ(im|oB3e&@RE!w*fV4o7}i5R)VO9rG3dr%EYCYFCJ6hFyX9 zA^Be4wvGSF%G6!wX@Y>4{A(694>l43I#VfF;!ej5MHV&3%639ifUF$87V#|iZsWn9 zSb?EUCxk5|me=@N#4Q!_c$a`&KZGviEKy~Uh@GZ|U;A7I5KfI)AsQmu@zLPE^fStj zn_W_9E$*f%pbj0{?_&BS-|>B^pL8mLmX1+PBW*y|?|yY^s1u6dv(21c^ere`S{rZo z#|x_F5gH?7wZB}n^{gA!J{h7aV6SlK79Z~y^rc2nN9E~Okxg;}s+Jd^f8k1L+vWCE zE>N+(H7TE$I&+{p6{g2_T+wF0?a#r%HE*}hp8Tk~R^%lo&F23|GW4^UW=ZlTtpz~& zKWHaz-?q3k53VIDsh!g5@sHYZnD{3QQIkm^l=WtuUV8A?8)t4l4v&4!3hz`?sh}yo zfBU4_-)tRKs1+0Mm0IV2NZ3jJDwv-TKzUYqtR^5{Rh27@_q8#Hum7W*bc zCOS0QWtuGJS-J*q)%VfB+GJAI-Vk&n);K>MMT*OFoqjX48djFG>cNDq!bp=b>b?uf z8T)_Ot<5k98nzv@_fgQunI^zo*f%blj z$CcPN>ZjEMMwprHPkrM0dqn!0vvspmf)Uyy7#|jq7j!xE_E+k6{LFR)&NcRX!kWeU zoJHpU8dngy%{bz}#wGrb>JlbrF^B+Er{o}5QSw&T>{|4`l!*)kn&&-lkfWktBt>cH zv;=%6FO}Nn9Ml@ugCm3@oOflfcOafs_DYt8X>eqEGhe8-DcGH_nCrmmk>C-Ch>51{ zz2jy=@HGmXE+7@l&mj4n;K$VqQ}NGHXq{BZloQ6Vls5{(1{CKb(XG42ErS7?<%Auq z&Rv6cq1n4@%bm&{WsjbHe-e|xWuv?74~n8f#W=(yF)oT^E3k&`tMRKjxrb`qg~4M) z3k&(F`pHQXMK!a>q7jfOe*|G{1;v@y4)W3Fx+WLseuhd-sQ(sE6FtrH?x607>Q?{4 z3*kWKh3+scl;|8KQLid={_B zT zhsfgdoDKR&(HPJ@OxFE=NKTt$v8{AE`#_?suwQlX5^IAzjX zC(4Cr2ToPU&7#)GgUH_V>(3S3aGr8kxsnxdnsm(nfBxE^g0A42~ImI9?D}8KUBL7 zI@Rg$TUQr^pMJ7hEDv@z523H1BOoVFGSh$SO@3m&0b~?wmYi}@Cun+$8fk5+FU&J0 zDh6Sg|LmpYQ=H?K^=hxb;ktncVTZwCA~IRz>3{Yvi0lfTtT_w(=RIiO+S7YOq1PC+ zrpjX?xnAhg3x=ILp4Kj$jss5KN^}ZG;qdVhV}zU&ilCkP_(N~Xf?T0YShCU3IFJ;T z9K;n>(8OTH_Wq}Euo!DY_kRi>{WqF1{Bs{MlRH__0DkC_7=huCT!^Y`DaavVwTWvS zj(F?ADQU>!G-~TqrL-nAI^|sp7VA?^tD9h$yMv?l`Qh^I_dtD2g=&GKl`WrL72Vf_N&jEnSbKHFCMwRpDV5$fVttVWrUNDM;>l@@liHtqSpi+ zNi2I+9qP~uEe$9`z@Fy%@<4r~Q&4Wuw#jt~R;aR#Bfj%@ZCP2r%I zyyQ-kK%VL)oC`A67jj|xuROk42XXWSP@+J<5maFMKMtHa|Iz_f=nMDA+I&P^E`8VW z$H2?OvJR(VALP(2U9t@yUZX+PI^rTa2M1Pri7oV=c9heR`6A?Nvd#DW26 zW@&b~D=grQ39*SJ32w`(#}3C8v!;CjhCtwFS}DS5iN@^KGZ}zTe^@a-2aon-ofCVr z2Yb(9ji?etlf-HT8gUBPovLuUaLA*Wn zQUb-ZL+m_;>Mh8^jAWg%?6TkruY_Y4coYY~JPXcUN8O!qjC`Z(S`x-qX#FupWOJz4 z=r)SicUce|Myw_`Xe0PJO>NC!2Tl87Ch#YR~OL99o({7aASaZuV0}`{+rg z<sDdp*$9rzEvBisH+PaO z1}5@XG?-}fNuHEKdoq7~P*;*41-2FbshGVY=WvA8hsNu*_2?l2V z{eNWtzhuBU7ZqUL3tbcE(<|9){#3`NKt+T?LT;S^&u?%RVs)8pwnVaMQAAL|%4^=1 zn%&iE9%_7nrjHJmigj0{?E%LMXU^2!7kCHx#;yE&9kLEwyceG_dfci_baMECnj}|3 z-Ud~Yp9crOE!b-BKCV~3u|V^hrxV6*vnWiXH0FvG?NoG*qi5MtTvo(QK4^C7XfO&& zzEC{l#sRLX-s}c7YQ)Ug+a@`c#fU+yT@)j(+C-KDs;RpYRnQE6z|8i_PJY{#=6)6( zxAsX!=O(19P|J2|6C~!fHv<3^{sFR}w4oQ=A(>>dc{`=kAJ;``S`cPpKs3e7xbO(JD{MCxQAymsK^3sdW?r&g$)|=Oc6?M z^GEd2*?eATLQFK-9wGwV#yY|`(0<(6pSTmBqS8Zs#IN9BRRgTl5uD)MZAt|enUXBT zwW2kTCrk-cd=W@j=sr#Ne1{_^(l|Ei>$rYJ&!lZJzg$RIc7*gVc{0VnIWhER?uX!j zDZqov`cFkA3<_7x5r(7nARNh{y-qw%{uc5ACIrG|bi{xSqF6N&@0Obdo(oZ)cBWSI( zhCRr@;o7(BFAVACXBoSaSth#Esz~MnIK=~;K}HAEJE$dxOVFBlEH|X0K>Ag&u$d&^hT|>_YQwsw`Q&f@(E)`~(Su0I0jDVY zl_&5DZ2sYtY-PAcX01{5e>|YBhyZ3mVE8wDJ16ln-9b88PsAn%F~VLEhc)H@E*s>V zb8Im?kF-*yfaWrC&0m%)^zFOG{z3W(r#i(Oiyb=b`e(2QGAyP*W#uU!^KXo zSouUBM7%?zGj^81P&a%_EE|8}8rUfQ{+HubJ>h;zJvEqi5&6z5YMDP5h|0zk`|(yk z%ri~6J_+)<&;x)o_>o66s>n6ysCY4U_RO}(%K%B|16@pkb8Oq?fH{SFl)`5?FF<9f zNeijp&Kv%J>%g6}bO-;xyJh$PUBfPZe!!F*NCf3`*-qVlsr7709I2?3b&;5cE*PU* zM+E~ie%&Igc9X)An>Jz<=7v!~Msxt?x^T#LAUoZ(H~+)Rst+L43r1j~zT?T(;V9fC zh>JiAyNS+y(xu2Pv!6K6rf$18Y%B8{f1ZM|fhS=Z9vTC|kx2ikgt?`(j6bSd2M{N~ zIr%+)aQQ|wzNhTtsW;z#DwI&BZa5)HhEhU`61B;kEzO#BdrNr3*^e}KGaW5d5eg?nBHj^CDGlH#x1)`LxJV~AwXSuugU@g&7UrgjhbW1zMQW%2x*)ptUKYuo3*kEGHkeOA=7GzL_siEujgVJqrX_)PO z8R73uy;fjxf$zxg5GVHGFzG=-y-(7f?P#FTcn$U>ubG~w4fm(5kEgzLXE26>ah8pZ zZe76NX5VX(5<=^7*v{!3bPHMGsE)YC<}?dJ(RPf69k~QR!>A_XN#srt9>s+2*{x0_ zZ#nTY+E$$;uk^8n_2;5kw!h^^fWJ4&C}agu-PD3siD?QPX)y|!03MK;{m!h>9$XVt1O1>e8k;|Ipao!;`dIpL$R2001P69|5n zcbu1_0*D^~`-?ZSS?Cemswi(sBKK74AwU1Jj>F~oj*7!SGllW1Tc*%-2vQ|l$-P@9 zG*iE~YKo!^G5-!>BmCxW2#-?9lCN=+?i_V7>9WAEG&Lq_$uGLh%&@?@6F@g+%@9qq zBPxyBoegh@B}!+!aUq!(!9XzHSDALn@Yt9%?<>>+*x4nv!MzcFVdx6Rv-wNI3w!fx zk(793sWE3}lt!@~mU?hdDft`R1Lw(DRm@j$;Q|b;0!SaEFome-uCG7*u7cvs`ZA#e zu%NLj(zf*wwC>c`-7u+L?ZBg-gK#{Te=mnQ%w{+~9Ee{z{!o)7w7%_3lX>`&;??MB zX%;pB_~X`tF-mrDC%4Ffu2JCU>>q_XZX zwVuCRs91donsxmcV%eIq7~}+HI{tW(zFSCKH+q_}Ip}0Y*_2C>dj0zqGjgSAFvN_R z1FAzfgx;hAZYp*PXv4_rkLh<4M&Dw1^^1H0SQPq8(s$V|@=%4Oz7)MfH^m`qTf9Rd z#5X)^n4^agSNM1a6G=zDhMMgI_=DMpqU;G7`z2TSj7Vzjr!dA+>`|8z%QKpfj7&ax zmE!9iK6F;S+5tU-9sGwYESDVePLq6qJt(cOkx~Ghq4o*C><^m>8;wyJV_0rgDIF7Z z%Eq|Dd#wL1pVc(mz;FK|3pxLT=*n^m098ZfMG*`^a`@p=y6@53WQUe>7g{RGK+awh z1qBv7E}KW!)}D3345jQ-596436abX6JXvHN)jwX90>x>wN0B@(=sqp@^xOaI=8*oU z;x9ITA%P7<;U57+gb7hSjLdjGjLZ}B1~N_(W$zr(<9k^YH^F}o6R-n0N=^!B01Jcb zcrLmslmE{ctB7tEeD?oXZM%8;)Dowt=@sC(0YGUbXoV5YDv^@XCGh8#H}xMSgs-fz z-O5cS2oFwLXAwrQvIwSS$s}_EX&5O|V8hGziq5{k3+TJR2vLUmwD=ry^}W9hQB}i@;hrdu{K8hp zzP8pVTb_!vsUY1hT%y&gSiG{or@1DzD}Hw;I4dJM@4#VnjvvVS<{JVdxvW}odeYWs zQ30^(!v7(F8QuekXBvt1Z{N_;Z9P3TJ*R#@5o@o2AzjPtPQJXX50v{z{J~up z5Q=GX-H2_f`qffcK%bqWIUUT;rI2UILz-`aK0W>Z>;7gpBj(u209#hpvj~7q^wQM* z^=?98!2Mi9JQIW3uS|0)KiN8)_QzKLJD>HjW%^FY*1{IaxGxdFNraXoeCfC4yBxmO ziAJpqymZIzeQ|N8ReGL==Cx?nO34LF0{q{+d-7j&4hptPm8AM5&Qj{rVxgX4o0L`6 z9=o=c20qbLV&bMBVXb@w^uGYj{8xH5drIi2dTa#@`+PJ4PWhiT!h9c1<~Uewyl36o zt6i{}I9a;Bbm{kx!uMqdc&pFU%dr%LX<`~`C{^04wqG;NVKA$g|jGU-_z%)Vo5DY_-(ImJjE4KUjuBzxFTtZpM4eN>y+BJNieaG)SbDP>XBy{|asf zDYhd%*;UCEpz6G!h4FbgzgTmemcpe{K~gQPi~U=h(l)Rk6XKwLDNkSOg}Ts;@4 z9Y0In@iNS8`7duN(^aZLDoDt2CSTVERZD9t&v`UYI;b8bf7guByZVzp~%Y{cnJ=uAeN|mQM3uFZY6cEpf~!{uHN?iTpwkMT0cTWLow4E%-;Ht|d;R zEhJKWMZz61_~*79&C);~MgVsN2*47Ybx#hhRs3VRqZdD5*oEUOF&OR~q0--Uv!CX_ zC@3z@;5ExU4tK%6`k^Hv4vy-UDDc+@um%-yLxYsg&jDk~UUElIANDW+uSkImI_Y9H zJXDnip%$IpX2a=KW74GWHJwV+U^m&`Qa7F&=By6* zTUoJw@XP(VM^ZAT9vsE#xW&iH9xi{rm2`UD3W%Y3{-iUlqi;M)O~+w6D)Z)Z`GkGo z4coWbv!@g@o7rfjg$_uyY?qOYpI3-W5ElP%N>KhXXNz6@pW#a-Z5U7cuU8%X zuUBcR$pWAg7)!?_q5d(K>NGL^etjs3`UvdWWqAjGmW9ni!_CX(Ka4W}denZ?j7`0S zVvKUuzkI4SqyjwTpYZWK_HEwdZ1>(w=|9RKTRC8Pp2K!T-G8=FDTG-(&FmVcK*6pVw=$Ho>_%Z1)Cm<`ITeP`3q=5zz#i z7FF9mwtTB167!6Chy@(AHSa&OcRbNsRwmvqQ5d%mM|nh{p`;J?Co3dzJEd&|?Iae0 ztM(BZ@3^8qMH(eDR+xf9=VaW48+$j&rZzn~r7$zWHe36Z9HJ+}#mozuGS8s$-_>e> zb;Z+N8a9#1KlDU>_x+nRTo_(HaL&6I62E z1+-@;*hFgWNR}74PW7T0&3fF{tpp#ztK%qxc#_5Z4aCaWD2e6;79w`1Thgi_D5#ad>n~bCtdnfU^L)C~+C8 zaZOd8hy-WqK0s^%T2DwMpoZ=V>xl5|J5+a6+T15<`4%Y7YT|@8d2nT}IqtJA!umDO zkwkT5xOK8QCGkC`0ndNb$LbMAs@QMG>9Sw(OCn(X;9gt7vSPL;aBzygR+5{guk3kW zsK#-qB=?g)@(#x}izNHwP%C6j_`hzWQSMVo{I6t+q5j9MhBcW1+OYlvnz&zm7Tisn z4)TqT5c0H0q(KHO2GP}c$H1!LNWMYD zWDnFH+GegrjF_9miEKd=gsu8Kt|I^JtQ`b;5ArP>?i3wBFn1iD9Nlp22;*x-+H7hD zB)0e;AxjBc1sc8Xo3)?-0Xtb5QySTm8y@b6zNVi)mHZmQGSq=Y-0ikrRb|7R11^@N zz?_jPMt>Dq>R$xC?nqrIws8yINm>zeY8~S)M&*v?Yij7RhH_C{jC1XrRrsJDyQFj5 z=%5z%9O5Ma=WP^b9(6%4mGa=9u(UOSTclwEx6t$%a=Ghd`C1jurL`dy1NHSQ5F5jj z87&=78fFsXE;uyu3WK@8@A3ZIEPqQ0yPI9HU)N+mqj9ii^padyZ>q4kG32S!wSMn8 z&dkgHjFFeSA=6-cKCY9mFiW7h>wL+u)DP%EPT#!+sA&`f2TK1OSn~ey#T7|9S7CnS zWMRlXe5)nMU~Ac=n(myL%&j(w9%q-92WY}oWA;&Dv|Ye zZ6+;(j+==#;82z6Jnt_j+Lb*=R^$c+%F2RbYZKX6w|@(>#1!(Ww?W-#UxOjWimuZF zrhSnEDk&XI+kHsgbNttihP51?2BOspTQ`sps7#I_p=w4Q4Z7a zTHa!j4asmfPs)B~Pmo-(Yv>mT$kC;a^yWS0^tsW5FcYWGg6bp8(8`2TOA79zWWwg8 z$s#+&xfzMQc$?FCnsYmM4={Cq2zo`Q3jVl56noE(D7*19kM5?mN{AfPZZhL0p-oFn$~{peJ*CFVSAYZwCEPPGQ3)H;=F5jYrSACMR>N$+&h@S~z+?aw zZL0VE<7JBD*JKj}$+-vlWE53()_Cz^@4-pTj3nxC;|nQza`&cJ1wnDIZ3TpF12x4} zvo+0tRh0(|uV66?!R-zNLeST$Hl=?AT?kbsFZD$AJMz8|bKDNOhQ^@Friydp#p6!k zZMZubvJjKolhE#1u?DL(R0|~y-~>%b)>3Y(r9p`=1}6l(>9VhzGU|5-&(Sc%m<2mM z1s$(M=QGfgh=AH0o=wgtg0I;nP0^y_lR4AencOpf5M-pVk4neb=vIwYdYXjx7P@fN z#Y32Vdwm8z?=_R0#=N}~0vlyfOdf<00G*F{%s1{bu;H#bRUs%jy~i~V$R@-RcqR~^ zyVQb>x8{W(PrsIoil~Y`0eQ$eFeDUq%{06O;41svQ2K|t_0@Un*N=zP@o8(Ew^No4 zM~MY)Zk~bEpF3r~oK>38@khkW$0ck;mdpwnqe^x5X}69P3ifigi!zxdyVLf6YH6y+ zxFYu^Z@*i$kic%2Vo6K^>cS@yvbDU@N{9kpcsC=+a{J<~yPt^y`(4Cq@1pEzRdmV( zs`SL+F>385vYf`P_)?4f@@fT&Vi!PSy=8sTa97eDcfH7?`v?qC`jyqeG$S3w# zMP}ur^K+L>3}9AcQ!0|th~ZxCT!|W{eU9EK;8)qHLJ>>}{GnNINu3+(@t=e;1^oGD z>H1Ku$?xXR*rt8}RRsZ&b|*Y}a>8UD;gD4I_LRb=K)4nJZ*%ehNL-}?RZlBEMlq2F++t2s5yxXr>)lq z8_9#$_78KVxK<)opPK&Tn`E(rx*@FzS2zP%oiKApZDlU%sJhEd$o<2R~iG(CDsVD|!lkJ;nuHfEMosRE{yxo61;g-I= zCYHNuQ?z@dIgjs{=(Ag(|LJiKi;afdt+1LG|m9|0^O z@vp+mq|f{x`58l@`5RogksMv`>bL@DUmiCg9}+;wXx8TE0{B;V^tDXG7rZGM^hQit zDMB7a6$UJpv&&mw+5mTY*VFuMvIS|SaTop0EkOTTD0gPEks;e5E|nKZZk=aSQH{(u zVtoyQr8b-4L)8!cI4DkjhIJYh#U~mRRRyh2%JcGoHI56lI1 zLO!k&EHl<`=Obo|eDQ>IP6`ZSQv@oz%Qk#<)K7k;*Yo2r!L|>o^%FiWhE2+yfh^U; zSHJuV4tPE@=_}1%8O3h?|9Npt7pNZE3Kk5E6YbwPMxVT0@;#YEoDQHe>4eUTAy_6` zE{nc~l7)=J<+ylC6Ck5nNmp3)OXPdP#M*vGCeMR_g*7lcaruMsgj{@y_yjCaq?uj`ddFoAvzGJFy^!&%5G_RpP7gTc3@B zuz@F)Nkv_4rB)#qzz0Yind>#C5T%P2(Z;=T72xBIS+ySpY%9x|XSk^2#^9)>oy3ft zF_?OZF1s=xjs6R(?vs>-eK|si6@0KV*A%U+C5R-fQz-n3K@7{UVSsDgJ;b(@E5~j* zOjSGB(9awX{S^-Ee^g+yu(3!By(+@3>=rW4${#99 zU{iVD_bzg9bAyL$a(hNhgY*&-3)B6y9X{h9uQduP=~f50^s-mQlncI`zZ0cL+0_xf zg2%!eAxYV|J)H>zRRDMW!d2s|ch(5DOz&E`nrB75Df@KB^+DSwNLM!sJ1}_pPMHq% zmC?rxx7g#uM;Bct@u0k2xKhPCm7)0!`nHWVHjhp zqnwW8E%&S6ZCCky;H&+O{ayR}Ui-0I>B|w|`rC^&LA_=Ag~T0s3L7je@3T}#n7$O| zdy|DRi1spoWg4Xqooxac<}yw}Hw0rTVD@1>@#;A;_bWj)Uf+Gl;$22W_`Hs_Owc~G z!~`ON)o6)X?XZ7wVBZ#t1Kzna=l38rYK2tEu3Bku`a(AaY1wu1yOtzXiHVKuekTPx z;rmKVU@-?B2{Vfy&FK8S#ZCsyhj?QWco`IyS5_BbeRDZ_%gE2KeCe+z|CE>W*N(1w z!P;xh2}Nd;H27NDVMkE50M^)&mNDG#FT#5YPr+=BZKw%h`2}1OC_9uqS0VS490@Gf&N^8=OI2_gS4x<}JQvP}5!eF8 ze`$Yu<`h?PEon2l$eo(?)6r_U!6*q&Lj~q)|15qBw=?BZ1saM)oWmV~{8N7H{1Sr# ze=xz>`EGL3P(qpadyG~v#V26#3uaz@#B8!zUP9j2r;v__lEt`apLm?1f zNu0<|uoHrEV8+te7LhbYM-tN(wzRZ`E(=|Hg|a>9VY;P-(nL165K19HOE20&>D}#3 zPkOR+m-PS5NVa9mAPy!20ye#&h~Jk>B(RVf(1WY-YNYz(DZ@y|Q~g*yVx%)T3+o#iE^gQm zj2TC2-lP&UY29Wn9W}Op#uEn9ax>lE*q_#838V37S|1!V(v91!*M0U|haOKUBs0yu zRzIRQCiGN)r5=|M|#=Tu(5L!q+jTX>(OrFU*3Z2j7 z%$j?<+NYtl18oUipwVLTm!h>RRKw)PnceV_B*d6kM4hZ{4=tw^8ZDy>VU<1@SEWFS z)oeJ0Rx!DZ>vHgaF{bK3c&b@9)$aFDJq0zYqXyW@<|J-53Yv|(_3QwQcoCBW48eZ_ zr9u>CnhX!t*3iWot){h1RnzwsS_hW0rtOjAdI5-70rm#kSV8M4RMbmhN1;oZ6yZxC z;-SlI@h=ChOXI0{_OdDBjeDhsny6W$&D3J6b+?{2QdtjwwFv^7X{$}NQ%@Qm+HSAG zUao@;*XrrzD(!>=Gz_Lq1V=V*WK`+^Uuk17p^KnOXP}i0WzH?bNjW3#qTNFCS1_$S zHzJle-Ga$Iw!_=v2nBiQO4=xP_A&V<1$s0Qhf61udMXx1$^b&fNGlY9Q3E!;0~+n8 zgRs7yNSH@|_ofb~%%iDYgTmqAH4G2AntI%Txdw0jD)qu{Ht}E%w1NX>68?u|=rRZG zC|O@NLxgAM%03Mm>7#zZ&A?e@u%Q*Y7PipS{kbGmuxDrx{!|&Bl7c`oK}k0qCa}fV zsWgb7ye=0vvMQyq(y9_nSdkgZWQ}A{7+R$xOtUS2GP!I#(HM?rko}H9sBR@L{H?5+6I-b#|beb6HUhp`l%}C3~!`2 z2|jNq#<6vJQ#!2=AqM{q+SN0H7p4^@+ARihxRhlOyouhT(VOY5({*V%L2Qyn6?z+# zH$h>4yPg?3j3rg_L_6^;@vh3wD=aonx+PT?f~K8+Bz75WbdU?~JHwJ`kEliAXHSRWUN0DyHyeFs z+79RLTyJ4tp}Uy21O{&~F0Cg8 z@A1&5=sp4WX{P$u)tCL~&gxlken0#*sSouUcu!|{6+FCbG6p-Jr3W>7fIi101%oPo zon#8W?r}|h5U`Ms6dDl$aIQLkNTX4D7+MJi4_v+ZP@PJj$4gSrqPS)}^r+ybj!vO8 zWOBWkf_qm7+RJ9^FVN!x`iscCW~xNm{pVN$URP9`;3WOx*eB_SKk!A+&+^!zb!zXN0Vx%nhI&0skxlz{Hxr4hh7xieisG8;#M$g@%nvaujQx< zqMXB>TH_fJe=!gJ&@OR5LaHuc1P=*>pA`E;i=U<@+0~tvAd-{VQ z@kiv3tZ8q0=ue`Nd5QiEW1&YtS}dy9Lw}`vg=YR{X&q`DOc+`84zJke{ajjjKhj=FKWEm?0;MSYZqhb&LUmRL0QY7|cy=D+_6Vdd9L}lFNAs zW3;%PFIi)>c?D1`oGE|HDH#r<~c(V%ij9KTZEe~c-qW+KtEn~I z*0Z;}t*dAIu8uYjH=(uTE!;fbI5&vKxxs3jxz(OA3-%d;Yo1eo3V_nL@-`70+d+2f znJVvqjX~B(9r1A32E^=ASvY^vNW;6BmIX@3uo*@Vz5)XVcQLIhqZm8BC){j10x5x1 z$9wEcfT{b_PpvK2SMrs-PvgCO6FcWR*Yi_Q;#W}DDRTxs49%UWHu)Cqh|W>o_NwQMN;8*4-fKn zZZ;Wh<^u3~1X4`5OkH3GS<6}|%Q=mcjJat|pq$jUjW(5k!ELvdcTAwe^Uo`T+@8v0 zF_Lz!<6yot5FU7^CcF2``0{ZqoRT z{1!AKvAAe3dvkWnID5){5Z}yi6J~ikjMS*|El3KOU$gCYU*+L<+C>7>6(cucno=@) zpRqR`XIe6U!?nyf={xvcLctiSW@XK$?w0oULZiU%wyseagOsDA^%9le2M;X04eKy6 z8NJ_VjrSX(q#d`Dm1UHaxMf>CbFwN=1N?k|@7DM(#w?_QR`XC+xSSlQJ<#mo5AlbE zoF73X1XKE~p;78Lt?`rmlx=S7S_T!N zWDG6b)$lX?C5;sRGTfmd5k0Ij<_%Z!a15ilhrecL@vp-LORu_85ppPBY2%;;gixUuxB`~uS) zaNX3KF%!A0VcYUW>!OgqtMT)qmrzrB$`m=)YMOU=_y_z$QEz|bB=KiX^A-p3w;n4_ z5>R&+YNMI7Eot3u^3O04r;n=q3yps+Dy$|{HK3VD0C zFA3D&FfBeSsKUR4TV~TkVMr;G$4sQfKIr~||ETfr`Q_3{{q;RlVT>P@*3Cdm!ps;~ zh)cJ`(8QGu&a*be0A@9vf91bv{1^Uv$<(lC0ER;8!(`&jBn)FvWQrXE{2z?Q@m0oT zB1hqq9zH{e?8N{7HUJysXvujIZETt!fa4{p)-o%Yo9*9W!8CVJB3&BR4Xh5%tG#2@j4B5Asb0` zQoYav<`-|7`)oyOR57qwUllrkjO>63Nw<`td|(>o$!DdFK5?_zXj8zUYov=cs*u)} z+&h#)DU$ex5>Fj54JrbHz02jzrpqN-tqfYV^bRz<11lRF7u?WaazudhFM&4Cc?8 zR{9Ttx)`HGQ5V0%INBf2_;dCr5>@KN(Gyz2z&QY2V6gVvSBVgQH)f@?Uk)N1%%jB| zh#=}hQIWct>I?PMKQXF**)>V1bz*F_MjRZpm+7(=HrQ{^VDyWD#Qt^+a zULyCO@vDMJ^;S9aG$&8hd0LdGrIB!+R#w&Ksqwh-V%;cR@(^ue3J+7&zA@Sy>8Q)o zmJw>L53i~#zHh_79aZ6~&OCLE(B3>yJoZn?_n z&QrXq?j)JT)$6c-nh~q}iYqy+92F~(sv!VYz2PL?I7&B<(c2>-d5ms}1W(XydAf6i z-aA6a$LIr*kTd9$N9cnO(Z`rVE}!cmLL+;ePy3wW_h*Bu{|)6xRgU?g#0|chN!s|BnUi8m5Xd`dMUz zets$lG(d(_NbOVe^sD1EPtX#-i@ePZ7HEgu0?KVcy~XDoqn9E+cTd%CN9cEXdO1&j z5qnQV=sf*@{Wy7xg4%4n_KXiNuZEn?b!xThgSuY3=e3hQXSFI{BP6X>Em1?NMOG~# zYcum|2p~Q+1OmfnPQgxkDYLJNQ=&8SRRCxf7GYcMBW(R0FutS5q@)Ugx?DI zNaqQDhoDbez_6EZ>)Mb;{ksv1j{S)wa%mh3&k2dvQ;LOv*6L^0^$a#j^qINK^ZbD2TMAx33(pcq)5|@! zkA)OrJoN;h02S!fN&=eC8FCN9N3de01?fF?A$QQH`rLw2P!Kx74~^3jVIhb?p@N!! z`viYJ64Hv)+&*Vmh^Q5bt*rOC>n0I8RZ6Jv(=|@>1b=aipNxck5Ys2uL!VFQ`B^Ik zM><51Tvdo2OTHZziLZ`hV>{BB=WpCatAoOrGz*V!S^302#?MCtZheoJza!#~zgNWj z{weO{5Mu1d^Y|x8FaPvreZ7-IicfieiuM*(=R4nhH*Fu|pGU0V`ei*h_*I^NYkAS1 zBArjs5`;Nuyn?@#^z_Z6{Ms;SJAF#$FwaMR>V$F6Jg{?^-9F_seWxxjF?P-Jt_R0g7CD|G z$yLR53mKRPPSHHH3$sn7!LJa2N{X2IWo;kr{imCKbK z*0V#|Z9Th`nDy*eZWK>6F;V>2Ks&9*W+O?on;f*CWHQk3#K}cNXnSr%!}BgQI`@!9 zsHOBt^3Z)Wi|(h{^fj78&(U1gsFJIwO8PW;rH5&rbc(8_$7#M}F)eWTX`y2U`5d)$ zo?{&?anl73Ov5OCrZV0Z>Z^2&?ZpApaBq0F5b=ffF17B9qai z7Jo@@b97;BY$P!*V{Bn_b5&FY009610050w3w&E;75{%p(|dcHJ=(6@(!H{7gRX7b zy;?>XkF5hKlrq+hl5J9MliT)gNp88hSvOuHA|i?cg4%&7s8nekCSg#q?9n*>jVphbUb=!yuEidjlr(h%AdNZdlSuXYJ)#`mSU&N(+e|J+PQiBi?Ps4Z}9$-L$&ZuoEPU)_<4R zEv;J~h!|TdFIOQ1v^FbghmAALs39=Zvb*cMZ9Nh->icZHx7V=io8t*bk4EP&R!|@? zd9%J%uaD~S?)q~(HydF`L7~7bxtes$XnojyP+9~J%DvD~%wN;I@TR|fUQED5{+i*%BuwV7Spx1VrYpl74GR1M1%Kpv^52xI zg)QS_NY;6CjZiROU_!UytkF4;>1a*P5@s#+;y4_yVF9WHrX6P4=5WrfN}AD#VJoQC zP{ZOCV%Mt2jPV+zj}x;1>X=zQd76C?j|c>+jy!lIZAKKaSd1ksZ7CUFXKhi@AfP%{ z`ji(Zuz(sY7nqkff@7pe*M9|BA~>sidx%U@l&)OfRhx zm_){|>`YkEq+_hnogOb%r>#3(pyKE$q2Ob5A@y+BNKi&DqMYom`p69We^z`1@%#*I z;zC2dYsOg`&cxXQ#~&k+f^$gQ^uaT&7&+ivPRLfQ8B-?OwkI4Trhi~9dCsuuUYC!m zy@s@ONLNFTkp}Cqz6fnNpYC{9|uwJwW`vf+e#1m@&T*KCPlE8?&?c)etWv5j~q zys*<&km}`!i*u6|jZzwu)P#bhK#>vab+)$<1^GB@p*+cJWMCh*Y1j(N`l2YUuz2EF zQnhg7_*!r&E@OF@XQLsxth8-ivRCj44OemX}SY z@Exk^j27?FJW4Zy*|mMO)fbOidZZ~cfCx0?xjM&EKfjM3uzvSY zHYekkn7u9B{EwE+qlOXTeb~i>KTJ1`bp70&)-y$Gb$khMM=_RC8H64+;aR~eEt2Dtv!A! z2b4`erGIqHlSA2%DR_oFlF+-1^)^+R*;V5T#dvT00?%srIes~=(PppXM>g7Db51`e zAnH`SK*o{D2`e5XC4ZBx0FrWDk8Sm(hrr)*w=kScQTO6y_QFeeg><$Ob#a;vRJ=yb zmSb*PWSB!`=eT1U0Z66S@rFj-Nrj3hZV}@kyoL=;%^$>!MkK$pBax>eF+u+AWzY#>gZ;i%IJ(- z+nRcGyUn;bX~e_n>-`@7#q|FsuXUR7Ow3ihFEITu6*-EBh0$s2Lr$q4dJ(8NNEPLT zhJOYcy~!|cWP03+o20M2P{Jh#65-}jy74Fjknm{uub}zfLtWo?xNPT|nh}1ISE!|X?kk*iiu*9CMJlDKSNqGta5ypsKt_(l$-kM49l7X@66sSWI^u7CA!`Gv&0rAh95>irLf&y5z3F ziae>f@u>G^EMrp7FXj?&QON?9kI6Nj7W(O0#mof11ruCU=)#IR7E6pBqkq-t zlas7iB!>sFn5Nak($^;Aju|sf-xfA{K(#1Z_M_-?AzuGGX4!z>jf~rrw zAD%xT$FQ?;y?}=!Jg12rg)UJ8^Vu=urG!EK{bV^eQHn{Kr8#H$j1wNYC~B=l#bG$ zks-tQRSM6SzBp=4c30@eOSX8G3QNK!< zDq&zR=K2bwR&mgzt%!t{Yu9OPnF&x48Nx&XDojTz5;HA>%t;_eh)} zFG-vs7V>3ZA+y^#Dc18$lElK|$~`-JNcu*#pV8UWk(ieI0*b zPT=w3;kB>cTd{C8PsOQpMpPRUvsF)?-W>vEA7w`FR0PbDhf7g$*S zYw#alxfCcZuUtBCFi}AnRR<#&9mNo84k{Qcs%e421AZkmxGD3`44_s}T-7WJhO^*s zFwW~Nck?K50M*s2s~1eJJ!7i=jF~5(kCyrtrB6^x2MCiS?Eq?%O@2;)rC1AmQ|EO* z2T7KE5f9rKA|8oiaF8X-<`FP438`&JVk{oT5UM~zuBB`Hf^@HY?-k%=q1{II>b9IEZgF2OS`gx?35PvYO-ZryKY^(XSX$3w{@?sdp}b8Kc6I9wq??7{P^kI@B7Yo zzVn^)KabC^ee09giD(Od=a?>@J9lWulPyDf=9FP&Te@2^!!7MC89P5_=5)`rtz_QL z8kl#CoT0l07DshAnHe=Qr`$r`ZRsA?bFR_eGB%RT>tjh%WMuEoZr-+K2)xdY%KqU( zE{6$sR8MZk(6B~K%P<_%8o}&o!*OvI=67^#?bwmb8mC*H3K27ZX@hpb$rulqIfH4n z?TmDeIC?f`be?hau`$Ex?6+J`&*g&TXIg$zKdpD>bZeyZz|ct};{~aXsV--aj2I5n znpBPbDYdwhDU`L(SUFqIGOeq{xVyXz*44(@Os?RXrI0x)VxyJX**NOVW!HO1PhWU0xWVY;1v?vQ&&nZnh_2B`%uFwUB; z=PDCD5~dB*s?mD7i>X<$7CbZ8*=Of+z!Y%m1}Vl=Z{)|k@f7wkHC9ekoHB<4;;_Z0yW4c2gzb!nXFCB*cVjeb8*bFHy=ca^Jl(XSx-|~Al1`@Kc0~7_9mBxLMm;+^Y+Bi|8aJHJ6{bD(fJVFNL8gu0A%cR` z&$K@FmWWgVG@#L5N-_Ce^9e(Nraw&k>42E;AmVsI^OddU!Z>y`|9`dJAsh*`i^u!h z=3698hv|r@A|anMheGrY!3p$jt6_{@+xDQQKDG~v4SKrc8P0~yK{}?24v$w7USGNXFm0=WlDBpu zU_ba!Pjbt)?ID5#*C-a($P^sY8}HnQ^o5MK+Z-_@NJbENFtz(a8fTXMP+JB} zW70{DM(GsOs%q`}t+9d!5r&=*QeLvdY~@RTc(XOKXsnVQur_NOijE&&$N+)Zv0j0q zM+J>sI*n*5jAeBXItQ`T-!J0M(zs~y1lTh<+cm=UT~ZNTdJ61Wr7r{`OZA57UG#3@ z{qBm^RX6S}n7OP}h40nqya1(PhNIu|3{^3{52o|%hm5nOJ@XJd9k4$@Kd907(+^31 zxkhi`jfUxm>43!Wk6=omV&Q&kV}0{?mlc7K_tKAvF7HESTi8Pj?EHZ2d=@Dj_L@6!9spxbj-1_(HoB<8pq|VQnE;vlSM~) z9mxv$2|AcAg4dDEghG&&Em7TpYUMD6!PIf(?a^V!&Lhe_r{GGKb5(EGAs35(?ykPg z5hvFjZFO6%O1wu?cF9Ji>FGRK=|j6!Zh@F+PsW2!<}HNir_i^}c=J%f^31$}ZpRoC zxk37wIR$i9!k^IS1$q&MyyRXK;Toa(2*M_(+JWZj8b2F4FVV-u@gEP<&(Y6oXqP^L zAS_rC-fGEhb5v86uhd; z`+H2cRl8~S>hEjxCGksS#gk(5|2j1%Zx4*SeD^+ z9y!#n0&5AxA4J}=ywW>!d8}%pf;Hea}8}C{=aQ z(qak+uHa^kn|S4a>}{za-v)>x`AaRaO3xr9)H(ZS)*xv$uhBTdYmox*Jn7Y4Gt*N* zH9+Dmq7M4jQ` z-l39XVZxk0bROh^RuXy7CAJlk1za5!uy7xGvyHRuI)*T@p)aXrm z5jqi~ffE53pAa8^2kOc#U3b(gxjGsP@nh27)@Y8a zBl>ZPXuO7edABXZ8K(HHp*o#KOSOg#=*`1OiPIr5Xrqc&FgcS%8F|y+XJ?TNm!(Y0 z*k8yG8O~u5MVnC-g7CJF8szUk_-c^9{wNxzC7?mTU@0v_&OC|HM)3PV*TmDuC#mro ztw;}lOj2|FDy>RfrAYf#T60;!r=Hf*y3!_o5L@a%7boIHy7Op}HeMucijo5pbbq2q zo6`fYQr*POZ^WbgK$B7%>dN6-6t;^ym# zf%wbxHl_;{^?#az=>cE+U}JB5iXIxcMtjqLDUMIlz6tsexPSovrE0dpd2C~G-^+A# z5qh2Vm|vexr{az2NqPjzUZ7Y)oquQI8XXq}AI13bbm|pambmVJj_MP>EfeJDSF4X0 zns>w!HHb3?iBG^p%%|`hhrR1yghOyoYD?f|b30VJi_~n~0=S}d{Q--pC zWuw+%T|D=>_E*V&xp8=ka{I55mG1T@Ch46G%jr|0?m)yZ7CV#PUiU0DCL;k~b6t_1 zoPf3I1I=}dE{WN{l8pF^bgoF>gZ<~Rf0CZ|Z4WdDlGmT7j%0ISYXjXpH%0GBcVqL9 z;=YR`{^gsB^!}p|kEstHJs*HWzR)gzaxSFz7wIQ1K&3>wNSEO3=Tkv3O8R)W-xnYB zCk6xUgLTQl#-EP(OBQ+|4g37;4W-2mSfH6!(>B~}Ey}adHvDdzw%1Z{1YocC!CtT7 zyI$JwGCF&);sf0tSN=+Z{DQb?gYUkz3EXC_cs8C`y9JJ&qRSU4;Ct!(BIy2q1~@Oh zR0b}ZrLv8;m!sqPYjic8-Ul^{Mf#-R?uOv*JU)Jn0h!OF_lsnhK3SySn4-@q;C%i9 zEt5xpP!&(?a6EOMdAEXT0ApJUycMO>5q!^m2Q87e~!OCLdz=tdVS*N*OTe8 zlitApiJR{ph~uO;ZnPKaFE7h~&kaPVhIs8&7Fwa`DvB!FLamC%XuG2K&@M&y&|XDT zbU@KVbX?KnbOP2T(rH-HQL+^+(0de>U*UT{eOS?wUo`jw*u?502s_$`}U| z?NjvEm(%;>=_?cwq$N%?etnYu76<``8vheA8jLG34+ zIE0g?cxjOvDmtv$FXl$(VxPE^yZzi9P%Ntu6ynwHptuzP`LcMF*9qDYSMonCalv;M zc|#N5b)v}aS9nvA?^eHmT}AHh4m8Bz{C!2fZ@a&~D^%a5MFQ;#1z|*}H^qHLe&8ZK zzEWEmY7Reryep)4;Zw)Q3VtwO|I9}zyi#lSKmE)_T9sTWlSWtQ3a2o&QY-SINU+`4 z90rg?LYLMRnz;GgKqT1hpCGqPyRXrk=_Wor$wzJ^BEd)?d4(T;ZsK>~`W4tA4~7zA zsyNJf?C51=?`;FJP3#7{Hu0mdC=voUH1P?1Ce;;(nmAjsbbOLWZd6Ntg+c=Na}mFM z!7FdXZ%~9r=p+xZspw9QIN zm9a#xjD^bh75c178Vi;2SLthNEL6sCq5+h#P>k8f4Kh|##_imt#zJMB;b)YdrMPGW z`ri($K1Pc;MLy18%qmMQBHy(;KmqFbTgc0HN6VPN>7 zO&pKNBN-DleJOxjNeT?j{|W_ z8WkQ=;J5)%=!=S*#o;;MMCj-ZP)!ZX1(J(A9eI9+mL}1INM;d=d1cnY$ml6T7BQX5T z4cz3pz+iEwv@I}DENy3z#q|s{dEQ2%_EA&Zg*7xj#tHcv zW|#?6_L=D*ZfL${eZmw;zm~-f%#C6e^8!;n1R4qgT3ZFYAd5vzXRv@Z^26~~&q~8U5XNUs|HN8FpFr7DD@{YIg-}Hh!IPjU^uF0llWjMfu$$I{ z*az_~JP96r03S-6h#s7U`S^bO%`E%*_5J|>W7uQxvf126PdpZKi6-GwF6Vr}Ws#Rk zi%JzH$cqGtThu5V(q$%GATyLpx5^!#&V_b3;AI-*q6~jjy(2kIMw4+&SsVS$&sSO# zaG3~3WYMI`AX;ToqHDB{-Xb0iPli#D;F>@Cz!-EMij|dktu!(?Dr_32RwNq3M=Qz_ zZFncD?9w^RV~w^A4F>xQu@<2gCJk@n1bYLiiZ9RVX+gsK4vi|+ax z`Y)QE>yX5GbXw0?()rHgp2v6Y?|;Ai6~Hta44Y4$EEhLYRc@>br(frOjAV;0o1acsC^@*n3r)y+I>hF1TIxce;hk#yN!}<5g)5iP-2MryPTMe;_5#3 zY?~{f39EjFts-NL=6`$fd!<&A)>c385EL}b_hc7TIt#4AVZQ2VN zn8;C%V-97h_=;pxPGBN^xZEQv^u1TyF>`Dd|Y+WNVk^$vUz2S`^>>OG|rn zzOP~h-%^w0;yXlW?LXSFFAFN=d;B0nJdh6>c=m{s`j9&f&t2!$-EFF>xC?`>kKH9& z>Z_j?I&y<UPbvwDX$1ozA=}>edT_lqch{0=l=KnAu5e7d~MWj}}oT0Cc(B(8|X zk~k+8tCgCfQd6mF$x2mXIIlC6QG;Qye@u;JvCN9+M!jN)D53-fY=h|ru7&g2`GxsxQeqA9qYnrHf4*AR zi|o0gG6K=6E~jLjN-9fT=(JX2dg`5Jx2jr6k?VSE&tC8l2%gafO{r9qQ~QN8e^xZT z&`&^+6vNb&LY?af1om1gjhU%~-0mt;?eR@KFoE#izf5_P@3>R{_W6zWe}NUTma!w& zAcT?E9qq-QVS__Pcf=Fh<%gO;=e<6rLf80(Gh93L_^e1MUFzdwUAZf=#X!!ztiEj}3Vu$A;<%UNb z{Gls8>ELO1eA2-)uK1#ZFS+B34!-P)PdWG_cYMmhv#$8GgFkl1ryYC+R$cI!&vtkX zo^;_6?2d!4!&4VL>fp~{!wt6^i}w=0J<$6HP)i30Y2wu_?*ae-0#F8%VW?DpcN0|< z{+>;n5W><>cF-6F+B9j{Et?7jw9<_RiYWq4lb2-ZWG2o`S{A`wL2+L}Tu~53ElQ^q zM1Jrbe(;a-c;8IYByH;9oSfWw@7?b%-+k}C_s_pK{{pZUw;3*+KHb}TGB_Y52DzFH zZVo0=!Ei94WplD3nX;xvvRaaV)4a|VE*YE_Gm;TWWO!oG$Y+h<=9HuuJRHoWBUvdI zk%f)q`sA7oYX?YIZ!O|ec}1ZKBO^uD(2%QXS>;@p)ilixaor$aG~e2?uBA1SR2q_^ilnBa z-2+E?!gQmKOyzWqG)*RHtLjl?Yzgd)4D;>SL&BZ0hr2Hs*TaMm-_#(VH9dyl6nZ4?fJA zfm$r^!Gjs%YmpCL%oJaLOBj6Rak#OJg5<|!!!&Hy{XX1}2fSF00CAvbQnD1D4?)cF zl9h)TX6al??j)G5?bj8ChQ_9j68$k#RCtRIE3i@kRuLf0%^peTs_F1Mw+JXQ;zJWc z!f`W0Ygx`VN3v>iU6qt#=C)^fATJUp|3n1sB~o!CLPbtE!%0ei46Caap*-S3Oe!~{ z`+M6drYKf>(SkJ$a|~W#k)gh^Qd`t*9oCB~Y@h^_Wy(9~40-Sf!@P8*6|vyMCP7mJ zHZ#l=G&zhPCmtH7hB$4|mCSF&Ho-h?y?Mq|mkjf(m=*~^~-mXX$O*ci)aJ?LOqG|BYDst;X`{ceU$_e4qmG@R@e zob-|_V_H}HoKW}ENjV|sV$@u?Q@sP!ODMvAIp-8D?!?qj(?;3!GYENkE^`za!&0$R++$U$bmN)Gq+U`Rd8KEP8Gq@Vt#l%(&ip*$_*Beto zLWxSNLKCPQ!xFOGCn<7LGWn!a;)5>Ee*+AG>lY1SEAqnl5aEnzagH{5Ifml`euA`1 zCFsqoCPlOBSc2!AX7xNnxJ}C|$$+Yv0numzh3*z8wc

yb%-OYcg6{P)b3;TV;9l88> zH7}mIwVJI>bGwUSXd1>bE7+mmCfKsNAfs{*q1f=$vJWmoDyVo4@x5=l%f2_Eq_{ z{BA6Js?P81xnJ+Xu|K?JgY)GVk*>Fsr|fExbxKT|W$*%csmL8}?)re2w_jwsE`7CN zdPumP^?|RoN!Cfca|6w7KV${y>(%{QC4POM?n_UHOT0=##V`Jg9k>(0B!2eaU70Mt zR_Bj~Kk^qU%DtC5{3lRH=IEn9ovj}pRK|3*D1BUMf2j5N#|^E=>ixJAf5l`cF5W+t zE&7nVj!oLav-`W<{Ked=|I9Uc#VNmXqrm11&+BKkv2%B~|2=<6BT!*VkaYd(wl9gW@ou&?~8$?-q>F2_DbRq&+0i&=i9=| zdF(fiTw-3{@Wxg4o$^k@66Wr|;<`1Z;`=82S1JzNrmR#H6|rl1Sd0Ao6(8e!b~+jU zH$OY&u!P=SMU}(zzr0t~{`$rC{Gh@7qW+yD1 z>Cus2cGvZ8(shp?dRd2+PwOw3=9{*7@~X_qg7bXZJaR2t55Ktr99pgi)i>(|ZaoMF zUV(X(XY%^>k({|3?Q9)@3!d#K3vO_d@z#A`qXg_^6#%gz1MmbGIM~fMS#XogMhG zY)}+k69p@36H}S&uvtI``QA=j6oqAyK!p)9@Jkkf)}fkoNC9k;tPyT2 zH7bK8`Zu#pR@|&F1HK6j=5)0^cRA1fKfSYEeB?@*l_yQ)N ziRXYO8ljjNrVUoSUq=e85Pl62(DC*t3e$AK3NP!agB2pK_dpMMe*>UGCE)c%{9uKM zv-;5$#+puU-0CO;I$}N;=(aULtb=0ZS4*G*rAgaVW#DISV?^O<>&cGW{AA!qHv>Ir zhhm7B{p9ZLkuu;zgn>550Tt+@DDZWhyl|)JWYHa_GVtS4fz~*pC~|TJD~j96F}Zn% zy$s@zNCpNI6orr7;0k~5Fp>eCUj#Y6h=D;7MPaTNRH3>I{2U#i`%t4&I|Qsq!C4a= zmhfX&fS%ArF-;^4sL(?Ov9W-GK?_AeU<6Qs^kjuy+>sXmHasH+)(LC#1^~+; yR9`KhG /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -170,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -203,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle b/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle deleted file mode 100644 index 575039e70..000000000 --- a/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - id "io.spring.convention.spring-module" -} - -dependencies { - management platform(project(":spring-authorization-server-dependencies")) - - api "org.springframework.security:spring-security-config" - api "org.springframework.security:spring-security-web" - api "org.springframework.security:spring-security-oauth2-core" - api "org.springframework.security:spring-security-oauth2-jose" - api "org.springframework.security:spring-security-oauth2-resource-server" - api("org.springframework:spring-core") { - exclude group: "commons-logging", module: "commons-logging" - } - api "com.nimbusds:nimbus-jose-jwt" - api "com.fasterxml.jackson.core:jackson-databind" - - optional "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" - optional "org.springframework:spring-jdbc" - - testImplementation "org.springframework.security:spring-security-test" - testImplementation "org.springframework:spring-webmvc" - testImplementation "org.bouncycastle:bcpkix-jdk18on" - testImplementation "org.bouncycastle:bcprov-jdk18on" - testImplementation "org.junit.jupiter:junit-jupiter" - testImplementation "org.assertj:assertj-core" - testImplementation "org.mockito:mockito-core" - testImplementation "com.jayway.jsonpath:json-path" - testImplementation "com.squareup.okhttp3:mockwebserver" - - testRuntimeOnly "org.hsqldb:hsqldb" - testRuntimeOnly "org.junit.platform:junit-platform-launcher" - - provided "jakarta.servlet:jakarta.servlet-api" -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java deleted file mode 100644 index 5791f3e8a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.io.Serializable; -import java.net.URI; -import java.net.URL; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.util.Assert; - -/** - * A base representation of OAuth 2.0 Authorization Server metadata, returned by an - * endpoint defined in OAuth 2.0 Authorization Server Metadata and OpenID Connect - * Discovery 1.0. The metadata endpoint returns a set of claims an Authorization Server - * describes about its configuration. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.1 - * @see OAuth2AuthorizationServerMetadataClaimAccessor - * @see 3.2. - * Authorization Server Metadata Response - * @see 4.2. - * OpenID Provider Configuration Response - * @see 4. - * Device Authorization Grant Metadata - * @see 3.3 Mutual-TLS Client - * Certificate-Bound Access Tokens Metadata - * @see 5.1 OAuth 2.0 Demonstrating - * Proof of Possession (DPoP) Metadata - * @see 5. - * OAuth 2.0 Pushed Authorization Requests Metadata - */ -public abstract class AbstractOAuth2AuthorizationServerMetadata - implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable { - - @Serial - private static final long serialVersionUID = -8817963285912690443L; - - private final Map claims; - - protected AbstractOAuth2AuthorizationServerMetadata(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims)); - } - - /** - * Returns the metadata as claims. - * @return a {@code Map} of the metadata as claims - */ - @Override - public Map getClaims() { - return this.claims; - } - - /** - * A builder for subclasses of {@link AbstractOAuth2AuthorizationServerMetadata}. - * - * @param the type of object - * @param the type of the builder - */ - protected abstract static class AbstractBuilder> { - - private final Map claims = new LinkedHashMap<>(); - - protected AbstractBuilder() { - } - - protected Map getClaims() { - return this.claims; - } - - @SuppressWarnings("unchecked") - protected final B getThis() { - // avoid unchecked casts in subclasses by using "getThis()" instead of "(B) - // this" - return (B) this; - } - - /** - * Use this {@code issuer} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED. - * @param issuer the {@code URL} of the Authorization Server's Issuer Identifier - * @return the {@link AbstractBuilder} for further configuration - */ - public B issuer(String issuer) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, issuer); - } - - /** - * Use this {@code authorization_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED. - * @param authorizationEndpoint the {@code URL} of the OAuth 2.0 Authorization - * Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B authorizationEndpoint(String authorizationEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint); - } - - /** - * Use this {@code pushed_authorization_request_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param pushedAuthorizationRequestEndpoint the {@code URL} of the OAuth 2.0 - * Pushed Authorization Request Endpoint - * @return the {@link AbstractBuilder} for further configuration - * @since 1.5 - */ - public B pushedAuthorizationRequestEndpoint(String pushedAuthorizationRequestEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, - pushedAuthorizationRequestEndpoint); - } - - /** - * Use this {@code device_authorization_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param deviceAuthorizationEndpoint the {@code URL} of the OAuth 2.0 Device - * Authorization Endpoint - * @return the {@link AbstractBuilder} for further configuration - * @since 1.1 - */ - public B deviceAuthorizationEndpoint(String deviceAuthorizationEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT, - deviceAuthorizationEndpoint); - } - - /** - * Use this {@code token_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED. - * @param tokenEndpoint the {@code URL} of the OAuth 2.0 Token Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenEndpoint(String tokenEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, tokenEndpoint); - } - - /** - * Add this client authentication method to the collection of - * {@code token_endpoint_auth_methods_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param authenticationMethod the client authentication method supported by the - * OAuth 2.0 Token Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenEndpointAuthenticationMethod(String authenticationMethod) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethod); - return getThis(); - } - - /** - * A {@code Consumer} of the client authentication method(s) allowing the ability - * to add, replace, or remove. - * @param authenticationMethodsConsumer a {@code Consumer} of the client - * authentication method(s) supported by the OAuth 2.0 Token Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenEndpointAuthenticationMethods(Consumer> authenticationMethodsConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethodsConsumer); - return getThis(); - } - - /** - * Use this {@code jwks_uri} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param jwkSetUrl the {@code URL} of the JSON Web Key Set - * @return the {@link AbstractBuilder} for further configuration - */ - public B jwkSetUrl(String jwkSetUrl) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, jwkSetUrl); - } - - /** - * Add this OAuth 2.0 {@code scope} to the collection of {@code scopes_supported} - * in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, - * RECOMMENDED. - * @param scope the OAuth 2.0 {@code scope} value supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B scope(String scope) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scope); - return getThis(); - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code scope} values supported allowing the - * ability to add, replace, or remove. - * @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values - * supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B scopes(Consumer> scopesConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer); - return getThis(); - } - - /** - * Add this OAuth 2.0 {@code response_type} to the collection of - * {@code response_types_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED. - * @param responseType the OAuth 2.0 {@code response_type} value supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B responseType(String responseType) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType); - return getThis(); - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code response_type} values supported - * allowing the ability to add, replace, or remove. - * @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 - * {@code response_type} values supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B responseTypes(Consumer> responseTypesConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, - responseTypesConsumer); - return getThis(); - } - - /** - * Add this OAuth 2.0 {@code grant_type} to the collection of - * {@code grant_types_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param grantType the OAuth 2.0 {@code grant_type} value supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B grantType(String grantType) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType); - return getThis(); - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported - * allowing the ability to add, replace, or remove. - * @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 - * {@code grant_type} values supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B grantTypes(Consumer> grantTypesConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer); - return getThis(); - } - - /** - * Use this {@code revocation_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param tokenRevocationEndpoint the {@code URL} of the OAuth 2.0 Token - * Revocation Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenRevocationEndpoint(String tokenRevocationEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, tokenRevocationEndpoint); - } - - /** - * Add this client authentication method to the collection of - * {@code revocation_endpoint_auth_methods_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param authenticationMethod the client authentication method supported by the - * OAuth 2.0 Token Revocation Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenRevocationEndpointAuthenticationMethod(String authenticationMethod) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethod); - return getThis(); - } - - /** - * A {@code Consumer} of the client authentication method(s) allowing the ability - * to add, replace, or remove. - * @param authenticationMethodsConsumer a {@code Consumer} of the client - * authentication method(s) supported by the OAuth 2.0 Token Revocation Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenRevocationEndpointAuthenticationMethods(Consumer> authenticationMethodsConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethodsConsumer); - return getThis(); - } - - /** - * Use this {@code introspection_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param tokenIntrospectionEndpoint the {@code URL} of the OAuth 2.0 Token - * Introspection Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, - tokenIntrospectionEndpoint); - } - - /** - * Add this client authentication method to the collection of - * {@code introspection_endpoint_auth_methods_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param authenticationMethod the client authentication method supported by the - * OAuth 2.0 Token Introspection Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenIntrospectionEndpointAuthenticationMethod(String authenticationMethod) { - addClaimToClaimList( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethod); - return getThis(); - } - - /** - * A {@code Consumer} of the client authentication method(s) allowing the ability - * to add, replace, or remove. - * @param authenticationMethodsConsumer a {@code Consumer} of the client - * authentication method(s) supported by the OAuth 2.0 Token Introspection - * Endpoint - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenIntrospectionEndpointAuthenticationMethods(Consumer> authenticationMethodsConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, - authenticationMethodsConsumer); - return getThis(); - } - - /** - * Use this {@code registration_endpoint} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param clientRegistrationEndpoint the {@code URL} of the OAuth 2.0 Dynamic - * Client Registration Endpoint - * @return the {@link AbstractBuilder} for further configuration - * @since 0.4.0 - */ - public B clientRegistrationEndpoint(String clientRegistrationEndpoint) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT, clientRegistrationEndpoint); - } - - /** - * Add this Proof Key for Code Exchange (PKCE) {@code code_challenge_method} to - * the collection of {@code code_challenge_methods_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param codeChallengeMethod the {@code code_challenge_method} value supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B codeChallengeMethod(String codeChallengeMethod) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, - codeChallengeMethod); - return getThis(); - } - - /** - * A {@code Consumer} of the Proof Key for Code Exchange (PKCE) - * {@code code_challenge_method} values supported allowing the ability to add, - * replace, or remove. - * @param codeChallengeMethodsConsumer a {@code Consumer} of the - * {@code code_challenge_method} values supported - * @return the {@link AbstractBuilder} for further configuration - */ - public B codeChallengeMethods(Consumer> codeChallengeMethodsConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, - codeChallengeMethodsConsumer); - return getThis(); - } - - /** - * Use this {@code tls_client_certificate_bound_access_tokens} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param tlsClientCertificateBoundAccessTokens {@code true} to indicate support - * for mutual-TLS client certificate-bound access tokens - * @return the {@link AbstractBuilder} for further configuration - * @since 1.3 - */ - public B tlsClientCertificateBoundAccessTokens(boolean tlsClientCertificateBoundAccessTokens) { - return claim(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS, - tlsClientCertificateBoundAccessTokens); - } - - /** - * Add a {@link JwsAlgorithms JSON Web Signature (JWS) algorithm} to the - * collection of {@code dpop_signing_alg_values_supported} in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL. - * @param dPoPSigningAlgorithm the {@link JwsAlgorithms JSON Web Signature (JWS) - * algorithm} supported for DPoP Proof JWTs - * @return the {@link AbstractBuilder} for further configuration - * @since 1.5 - */ - public B dPoPSigningAlgorithm(String dPoPSigningAlgorithm) { - addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED, - dPoPSigningAlgorithm); - return getThis(); - } - - /** - * A {@code Consumer} of the {@link JwsAlgorithms JSON Web Signature (JWS) - * algorithms} supported for DPoP Proof JWTs allowing the ability to add, replace, - * or remove. - * @param dPoPSigningAlgorithmsConsumer a {@code Consumer} of the - * {@link JwsAlgorithms JSON Web Signature (JWS) algorithms} supported for DPoP - * Proof JWTs - * @return the {@link AbstractBuilder} for further configuration - * @since 1.5 - */ - public B dPoPSigningAlgorithms(Consumer> dPoPSigningAlgorithmsConsumer) { - acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED, - dPoPSigningAlgorithmsConsumer); - return getThis(); - } - - /** - * Use this claim in the resulting - * {@link AbstractOAuth2AuthorizationServerMetadata}. - * @param name the claim name - * @param value the claim value - * @return the {@link AbstractBuilder} for further configuration - */ - public B claim(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.put(name, value); - return getThis(); - } - - /** - * Provides access to every {@link #claim(String, Object)} declared so far with - * the possibility to add, replace, or remove. - * @param claimsConsumer a {@code Consumer} of the claims - * @return the {@link AbstractBuilder} for further configurations - */ - public B claims(Consumer> claimsConsumer) { - claimsConsumer.accept(this.claims); - return getThis(); - } - - /** - * Creates the {@link AbstractOAuth2AuthorizationServerMetadata}. - * @return the {@link AbstractOAuth2AuthorizationServerMetadata} - */ - public abstract T build(); - - protected void validate() { - Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), - "issuer cannot be null"); - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), - "issuer must be a valid URL"); - Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), - "authorizationEndpoint cannot be null"); - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), - "authorizationEndpoint must be a valid URL"); - if (getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT) != null) { - validateURL( - getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT), - "pushedAuthorizationRequestEndpoint must be a valid URL"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT) != null) { - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT), - "deviceAuthorizationEndpoint must be a valid URL"); - } - Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), - "tokenEndpoint cannot be null"); - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), - "tokenEndpoint must be a valid URL"); - if (getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenEndpointAuthenticationMethods must be of type List"); - Assert.notEmpty( - (List) getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenEndpointAuthenticationMethods cannot be empty"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI) != null) { - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), - "jwksUri must be a valid URL"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), - "scopes must be of type List"); - Assert.notEmpty((List) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), - "scopes cannot be empty"); - } - Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), - "responseTypes cannot be null"); - Assert.isInstanceOf(List.class, - getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), - "responseTypes must be of type List"); - Assert.notEmpty( - (List) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), - "responseTypes cannot be empty"); - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), - "grantTypes must be of type List"); - Assert.notEmpty( - (List) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), - "grantTypes cannot be empty"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) != null) { - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT), - "tokenRevocationEndpoint must be a valid URL"); - } - if (getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims().get( - OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenRevocationEndpointAuthenticationMethods must be of type List"); - Assert.notEmpty( - (List) getClaims().get( - OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenRevocationEndpointAuthenticationMethods cannot be empty"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT) != null) { - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT), - "tokenIntrospectionEndpoint must be a valid URL"); - } - if (getClaims().get( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenIntrospectionEndpointAuthenticationMethods must be of type List"); - Assert.notEmpty((List) getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), - "tokenIntrospectionEndpointAuthenticationMethods cannot be empty"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT) != null) { - validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT), - "clientRegistrationEndpoint must be a valid URL"); - } - if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), - "codeChallengeMethods must be of type List"); - Assert.notEmpty( - (List) getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), - "codeChallengeMethods cannot be empty"); - } - if (getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED) != null) { - Assert.isInstanceOf(List.class, - getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED), - "dPoPSigningAlgorithms must be of type List"); - Assert.notEmpty( - (List) getClaims() - .get(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED), - "dPoPSigningAlgorithms cannot be empty"); - } - } - - @SuppressWarnings("unchecked") - private void addClaimToClaimList(String name, String value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - getClaims().computeIfAbsent(name, (k) -> new LinkedList()); - ((List) getClaims().get(name)).add(value); - } - - @SuppressWarnings("unchecked") - private void acceptClaimValues(String name, Consumer> valuesConsumer) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); - getClaims().computeIfAbsent(name, (k) -> new LinkedList()); - List values = (List) getClaims().get(name); - valuesConsumer.accept(values); - } - - protected static void validateURL(Object url, String errorMessage) { - if (URL.class.isAssignableFrom(url.getClass())) { - return; - } - - try { - new URI(url.toString()).toURL(); - } - catch (Exception ex) { - throw new IllegalArgumentException(errorMessage, ex); - } - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java deleted file mode 100644 index 15884b900..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthorizationConsentService} that stores - * {@link OAuth2AuthorizationConsent}'s in-memory. - * - *

- * NOTE: This implementation should ONLY be used during development/testing. - * - * @author Daniel Garnier-Moiroux - * @since 0.1.2 - * @see OAuth2AuthorizationConsentService - */ -public final class InMemoryOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { - - private final Map authorizationConsents = new ConcurrentHashMap<>(); - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationConsentService}. - */ - public InMemoryOAuth2AuthorizationConsentService() { - this(Collections.emptyList()); - } - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationConsentService} using the provided - * parameters. - * @param authorizationConsents the authorization consent(s) - */ - public InMemoryOAuth2AuthorizationConsentService(OAuth2AuthorizationConsent... authorizationConsents) { - this(Arrays.asList(authorizationConsents)); - } - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationConsentService} using the provided - * parameters. - * @param authorizationConsents the authorization consent(s) - */ - public InMemoryOAuth2AuthorizationConsentService(List authorizationConsents) { - Assert.notNull(authorizationConsents, "authorizationConsents cannot be null"); - authorizationConsents.forEach((authorizationConsent) -> { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - int id = getId(authorizationConsent); - Assert.isTrue(!this.authorizationConsents.containsKey(id), - "The authorizationConsent must be unique. Found duplicate, with registered client id: [" - + authorizationConsent.getRegisteredClientId() + "] and principal name: [" - + authorizationConsent.getPrincipalName() + "]"); - this.authorizationConsents.put(id, authorizationConsent); - }); - } - - @Override - public void save(OAuth2AuthorizationConsent authorizationConsent) { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - int id = getId(authorizationConsent); - this.authorizationConsents.put(id, authorizationConsent); - } - - @Override - public void remove(OAuth2AuthorizationConsent authorizationConsent) { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - int id = getId(authorizationConsent); - this.authorizationConsents.remove(id, authorizationConsent); - } - - @Override - @Nullable - public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { - Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); - Assert.hasText(principalName, "principalName cannot be empty"); - int id = getId(registeredClientId, principalName); - return this.authorizationConsents.get(id); - } - - private static int getId(String registeredClientId, String principalName) { - return Objects.hash(registeredClientId, principalName); - } - - private static int getId(OAuth2AuthorizationConsent authorizationConsent) { - return getId(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java deleted file mode 100644 index cc697df82..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s - * in-memory. - * - *

- * NOTE: This implementation should ONLY be used during development/testing. - * - * @author Krisztian Toth - * @author Joe Grandja - * @since 0.0.1 - * @see OAuth2AuthorizationService - */ -public final class InMemoryOAuth2AuthorizationService implements OAuth2AuthorizationService { - - private int maxInitializedAuthorizations = 100; - - /* - * Stores "initialized" (uncompleted) authorizations, where an access token has not - * yet been granted. This state occurs with the authorization_code grant flow during - * the user consent step OR when the code is returned in the authorization response - * but the access token request is not yet initiated. - */ - private Map initializedAuthorizations = Collections - .synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations)); - - /* - * Stores "completed" authorizations, where an access token has been granted. - */ - private final Map authorizations = new ConcurrentHashMap<>(); - - /* - * Constructor used for testing only. - */ - InMemoryOAuth2AuthorizationService(int maxInitializedAuthorizations) { - this.maxInitializedAuthorizations = maxInitializedAuthorizations; - this.initializedAuthorizations = Collections - .synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations)); - } - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationService}. - */ - public InMemoryOAuth2AuthorizationService() { - this(Collections.emptyList()); - } - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided - * parameters. - * @param authorizations the authorization(s) - */ - public InMemoryOAuth2AuthorizationService(OAuth2Authorization... authorizations) { - this(Arrays.asList(authorizations)); - } - - /** - * Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided - * parameters. - * @param authorizations the authorization(s) - */ - public InMemoryOAuth2AuthorizationService(List authorizations) { - Assert.notNull(authorizations, "authorizations cannot be null"); - authorizations.forEach((authorization) -> { - Assert.notNull(authorization, "authorization cannot be null"); - Assert.isTrue(!this.authorizations.containsKey(authorization.getId()), - "The authorization must be unique. Found duplicate identifier: " + authorization.getId()); - this.authorizations.put(authorization.getId(), authorization); - }); - } - - @Override - public void save(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); - if (isComplete(authorization)) { - this.authorizations.put(authorization.getId(), authorization); - } - else { - this.initializedAuthorizations.put(authorization.getId(), authorization); - } - } - - @Override - public void remove(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); - if (isComplete(authorization)) { - this.authorizations.remove(authorization.getId(), authorization); - } - else { - this.initializedAuthorizations.remove(authorization.getId(), authorization); - } - } - - @Nullable - @Override - public OAuth2Authorization findById(String id) { - Assert.hasText(id, "id cannot be empty"); - OAuth2Authorization authorization = this.authorizations.get(id); - return (authorization != null) ? authorization : this.initializedAuthorizations.get(id); - } - - @Nullable - @Override - public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { - Assert.hasText(token, "token cannot be empty"); - for (OAuth2Authorization authorization : this.authorizations.values()) { - if (hasToken(authorization, token, tokenType)) { - return authorization; - } - } - for (OAuth2Authorization authorization : this.initializedAuthorizations.values()) { - if (hasToken(authorization, token, tokenType)) { - return authorization; - } - } - return null; - } - - private static boolean isComplete(OAuth2Authorization authorization) { - return authorization.getAccessToken() != null; - } - - private static boolean hasToken(OAuth2Authorization authorization, String token, - @Nullable OAuth2TokenType tokenType) { - // @formatter:off - if (tokenType == null) { - return matchesState(authorization, token) || - matchesAuthorizationCode(authorization, token) || - matchesAccessToken(authorization, token) || - matchesIdToken(authorization, token) || - matchesRefreshToken(authorization, token) || - matchesDeviceCode(authorization, token) || - matchesUserCode(authorization, token); - } - else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) { - return matchesState(authorization, token); - } - else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) { - return matchesAuthorizationCode(authorization, token); - } - else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { - return matchesAccessToken(authorization, token); - } - else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) { - return matchesIdToken(authorization, token); - } - else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { - return matchesRefreshToken(authorization, token); - } - else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) { - return matchesDeviceCode(authorization, token); - } - else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) { - return matchesUserCode(authorization, token); - } - // @formatter:on - return false; - } - - private static boolean matchesState(OAuth2Authorization authorization, String token) { - return token.equals(authorization.getAttribute(OAuth2ParameterNames.STATE)); - } - - private static boolean matchesAuthorizationCode(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - return authorizationCode != null && authorizationCode.getToken().getTokenValue().equals(token); - } - - private static boolean matchesAccessToken(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token accessToken = authorization.getToken(OAuth2AccessToken.class); - return accessToken != null && accessToken.getToken().getTokenValue().equals(token); - } - - private static boolean matchesRefreshToken(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token refreshToken = authorization.getToken(OAuth2RefreshToken.class); - return refreshToken != null && refreshToken.getToken().getTokenValue().equals(token); - } - - private static boolean matchesIdToken(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token idToken = authorization.getToken(OidcIdToken.class); - return idToken != null && idToken.getToken().getTokenValue().equals(token); - } - - private static boolean matchesDeviceCode(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token deviceCode = authorization.getToken(OAuth2DeviceCode.class); - return deviceCode != null && deviceCode.getToken().getTokenValue().equals(token); - } - - private static boolean matchesUserCode(OAuth2Authorization authorization, String token) { - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); - return userCode != null && userCode.getToken().getTokenValue().equals(token); - } - - private static final class MaxSizeHashMap extends LinkedHashMap { - - private final int maxSize; - - private MaxSizeHashMap(int maxSize) { - this.maxSize = maxSize; - } - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > this.maxSize; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java deleted file mode 100644 index bf8660ac3..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Function; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.io.ClassPathResource; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.lang.Nullable; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A JDBC implementation of an {@link OAuth2AuthorizationConsentService} that uses a - * {@link JdbcOperations} for {@link OAuth2AuthorizationConsent} persistence. - * - *

- * IMPORTANT: This {@code OAuth2AuthorizationConsentService} depends on the table - * definition described in - * "classpath:org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql" - * and therefore MUST be defined in the database schema. - * - *

- * NOTE: This {@code OAuth2AuthorizationConsentService} is a simplified JDBC - * implementation that MAY be used in a production environment. However, it does have - * limitations as it likely won't perform well in an environment requiring high - * throughput. The expectation is that the consuming application will provide their own - * implementation of {@code OAuth2AuthorizationConsentService} that meets the performance - * requirements for its deployment environment. - * - * @author Ovidiu Popa - * @author Josh Long - * @since 0.1.2 - * @see OAuth2AuthorizationConsentService - * @see OAuth2AuthorizationConsent - * @see JdbcOperations - * @see RowMapper - */ -@ImportRuntimeHints(JdbcOAuth2AuthorizationConsentService.JdbcOAuth2AuthorizationConsentServiceRuntimeHintsRegistrar.class) -public class JdbcOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { - - // @formatter:off - private static final String COLUMN_NAMES = "registered_client_id, " - + "principal_name, " - + "authorities"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2_authorization_consent"; - - private static final String PK_FILTER = "registered_client_id = ? AND principal_name = ?"; - - // @formatter:off - private static final String LOAD_AUTHORIZATION_CONSENT_SQL = "SELECT " + COLUMN_NAMES - + " FROM " + TABLE_NAME - + " WHERE " + PK_FILTER; - // @formatter:on - - // @formatter:off - private static final String SAVE_AUTHORIZATION_CONSENT_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)"; - // @formatter:on - - // @formatter:off - private static final String UPDATE_AUTHORIZATION_CONSENT_SQL = "UPDATE " + TABLE_NAME - + " SET authorities = ?" - + " WHERE " + PK_FILTER; - // @formatter:on - - private static final String REMOVE_AUTHORIZATION_CONSENT_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; - - private final JdbcOperations jdbcOperations; - - private RowMapper authorizationConsentRowMapper; - - private Function> authorizationConsentParametersMapper; - - /** - * Constructs a {@code JdbcOAuth2AuthorizationConsentService} using the provided - * parameters. - * @param jdbcOperations the JDBC operations - * @param registeredClientRepository the registered client repository - */ - public JdbcOAuth2AuthorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - this.jdbcOperations = jdbcOperations; - this.authorizationConsentRowMapper = new OAuth2AuthorizationConsentRowMapper(registeredClientRepository); - this.authorizationConsentParametersMapper = new OAuth2AuthorizationConsentParametersMapper(); - } - - @Override - public void save(OAuth2AuthorizationConsent authorizationConsent) { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - OAuth2AuthorizationConsent existingAuthorizationConsent = findById(authorizationConsent.getRegisteredClientId(), - authorizationConsent.getPrincipalName()); - if (existingAuthorizationConsent == null) { - insertAuthorizationConsent(authorizationConsent); - } - else { - updateAuthorizationConsent(authorizationConsent); - } - } - - private void updateAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { - List parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent); - SqlParameterValue registeredClientId = parameters.remove(0); - SqlParameterValue principalName = parameters.remove(0); - parameters.add(registeredClientId); - parameters.add(principalName); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - this.jdbcOperations.update(UPDATE_AUTHORIZATION_CONSENT_SQL, pss); - } - - private void insertAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { - List parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - this.jdbcOperations.update(SAVE_AUTHORIZATION_CONSENT_SQL, pss); - } - - @Override - public void remove(OAuth2AuthorizationConsent authorizationConsent) { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()), - new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName()) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - this.jdbcOperations.update(REMOVE_AUTHORIZATION_CONSENT_SQL, pss); - } - - @Override - @Nullable - public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { - Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); - Assert.hasText(principalName, "principalName cannot be empty"); - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, registeredClientId), - new SqlParameterValue(Types.VARCHAR, principalName) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - List result = this.jdbcOperations.query(LOAD_AUTHORIZATION_CONSENT_SQL, pss, - this.authorizationConsentRowMapper); - return !result.isEmpty() ? result.get(0) : null; - } - - /** - * Sets the {@link RowMapper} used for mapping the current row in - * {@code java.sql.ResultSet} to {@link OAuth2AuthorizationConsent}. The default is - * {@link OAuth2AuthorizationConsentRowMapper}. - * @param authorizationConsentRowMapper the {@link RowMapper} used for mapping the - * current row in {@code ResultSet} to {@link OAuth2AuthorizationConsent} - */ - public final void setAuthorizationConsentRowMapper( - RowMapper authorizationConsentRowMapper) { - Assert.notNull(authorizationConsentRowMapper, "authorizationConsentRowMapper cannot be null"); - this.authorizationConsentRowMapper = authorizationConsentRowMapper; - } - - /** - * Sets the {@code Function} used for mapping {@link OAuth2AuthorizationConsent} to a - * {@code List} of {@link SqlParameterValue}. The default is - * {@link OAuth2AuthorizationConsentParametersMapper}. - * @param authorizationConsentParametersMapper the {@code Function} used for mapping - * {@link OAuth2AuthorizationConsent} to a {@code List} of {@link SqlParameterValue} - */ - public final void setAuthorizationConsentParametersMapper( - Function> authorizationConsentParametersMapper) { - Assert.notNull(authorizationConsentParametersMapper, "authorizationConsentParametersMapper cannot be null"); - this.authorizationConsentParametersMapper = authorizationConsentParametersMapper; - } - - protected final JdbcOperations getJdbcOperations() { - return this.jdbcOperations; - } - - protected final RowMapper getAuthorizationConsentRowMapper() { - return this.authorizationConsentRowMapper; - } - - protected final Function> getAuthorizationConsentParametersMapper() { - return this.authorizationConsentParametersMapper; - } - - /** - * The default {@link RowMapper} that maps the current row in {@code ResultSet} to - * {@link OAuth2AuthorizationConsent}. - */ - public static class OAuth2AuthorizationConsentRowMapper implements RowMapper { - - private final RegisteredClientRepository registeredClientRepository; - - public OAuth2AuthorizationConsentRowMapper(RegisteredClientRepository registeredClientRepository) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - this.registeredClientRepository = registeredClientRepository; - } - - @Override - public OAuth2AuthorizationConsent mapRow(ResultSet rs, int rowNum) throws SQLException { - String registeredClientId = rs.getString("registered_client_id"); - RegisteredClient registeredClient = this.registeredClientRepository.findById(registeredClientId); - if (registeredClient == null) { - throw new DataRetrievalFailureException("The RegisteredClient with id '" + registeredClientId - + "' was not found in the RegisteredClientRepository."); - } - - String principalName = rs.getString("principal_name"); - - OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClientId, - principalName); - String authorizationConsentAuthorities = rs.getString("authorities"); - if (authorizationConsentAuthorities != null) { - for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsentAuthorities)) { - builder.authority(new SimpleGrantedAuthority(authority)); - } - } - return builder.build(); - } - - protected final RegisteredClientRepository getRegisteredClientRepository() { - return this.registeredClientRepository; - } - - } - - /** - * The default {@code Function} that maps {@link OAuth2AuthorizationConsent} to a - * {@code List} of {@link SqlParameterValue}. - */ - public static class OAuth2AuthorizationConsentParametersMapper - implements Function> { - - @Override - public List apply(OAuth2AuthorizationConsent authorizationConsent) { - List parameters = new ArrayList<>(); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName())); - - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : authorizationConsent.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - parameters - .add(new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToDelimitedString(authorities, ","))); - return parameters; - } - - } - - static class JdbcOAuth2AuthorizationConsentServiceRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - hints.resources() - .registerResource(new ClassPathResource( - "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java deleted file mode 100644 index f8f23edb5..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.nio.charset.StandardCharsets; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.io.ClassPathResource; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.ConnectionCallback; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.jdbc.support.lob.DefaultLobHandler; -import org.springframework.jdbc.support.lob.LobCreator; -import org.springframework.jdbc.support.lob.LobHandler; -import org.springframework.lang.Nullable; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * A JDBC implementation of an {@link OAuth2AuthorizationService} that uses a - * {@link JdbcOperations} for {@link OAuth2Authorization} persistence. - * - *

- * IMPORTANT: This {@code OAuth2AuthorizationService} depends on the table - * definition described in - * "classpath:org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql" - * and therefore MUST be defined in the database schema. - * - *

- * NOTE: This {@code OAuth2AuthorizationService} is a simplified JDBC - * implementation that MAY be used in a production environment. However, it does have - * limitations as it likely won't perform well in an environment requiring high - * throughput. The expectation is that the consuming application will provide their own - * implementation of {@code OAuth2AuthorizationService} that meets the performance - * requirements for its deployment environment. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Josh Long - * @since 0.1.2 - * @see OAuth2AuthorizationService - * @see OAuth2Authorization - * @see JdbcOperations - * @see RowMapper - */ -@ImportRuntimeHints(JdbcOAuth2AuthorizationService.JdbcOAuth2AuthorizationServiceRuntimeHintsRegistrar.class) -public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationService { - - private static final String REFRESH_TOKEN_VALUE = "refresh_token_value"; - - private static final String AUTHORIZATION_CODE_VALUE = "authorization_code_value"; - - private static final String ACCESS_TOKEN_VALUE = "access_token_value"; - - private static final String OIDC_ID_TOKEN_VALUE = "oidc_id_token_value"; - - private static final String USER_CODE_VALUE = "user_code_value"; - - private static final String DEVICE_CODE_VALUE = "device_code_value"; - - private static final String AUTHORIZATION_CODE_METADATA = "authorization_code_metadata"; - - private static final String ACCESS_TOKEN_METADATA = "access_token_metadata"; - - private static final String OIDC_ID_TOKEN_METADATA = "oidc_id_token_metadata"; - - private static final String REFRESH_TOKEN_METADATA = "refresh_token_metadata"; - - private static final String USER_CODE_METADATA = "user_code_metadata"; - - private static final String DEVICE_CODE_METADATA = "device_code_metadata"; - - // @formatter:off - private static final String COLUMN_NAMES = "id, " - + "registered_client_id, " - + "principal_name, " - + "authorization_grant_type, " - + "authorized_scopes, " - + "attributes, " - + "state, " - + "authorization_code_value, " - + "authorization_code_issued_at, " - + "authorization_code_expires_at," - + "authorization_code_metadata," - + "access_token_value," - + "access_token_issued_at," - + "access_token_expires_at," - + "access_token_metadata," - + "access_token_type," - + "access_token_scopes," - + "oidc_id_token_value," - + "oidc_id_token_issued_at," - + "oidc_id_token_expires_at," - + "oidc_id_token_metadata," - + "refresh_token_value," - + "refresh_token_issued_at," - + "refresh_token_expires_at," - + "refresh_token_metadata," - + "user_code_value," - + "user_code_issued_at," - + "user_code_expires_at," - + "user_code_metadata," - + "device_code_value," - + "device_code_issued_at," - + "device_code_expires_at," - + "device_code_metadata"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2_authorization"; - - private static final String PK_FILTER = "id = ?"; - - private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorization_code_value = ? OR " - + "access_token_value = ? OR oidc_id_token_value = ? OR refresh_token_value = ? OR user_code_value = ? OR " - + "device_code_value = ?"; - - private static final String STATE_FILTER = "state = ?"; - - private static final String AUTHORIZATION_CODE_FILTER = "authorization_code_value = ?"; - - private static final String ACCESS_TOKEN_FILTER = "access_token_value = ?"; - - private static final String ID_TOKEN_FILTER = "oidc_id_token_value = ?"; - - private static final String REFRESH_TOKEN_FILTER = "refresh_token_value = ?"; - - private static final String USER_CODE_FILTER = "user_code_value = ?"; - - private static final String DEVICE_CODE_FILTER = "device_code_value = ?"; - - // @formatter:off - private static final String LOAD_AUTHORIZATION_SQL = "SELECT " + COLUMN_NAMES - + " FROM " + TABLE_NAME - + " WHERE "; - // @formatter:on - - // @formatter:off - private static final String SAVE_AUTHORIZATION_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - // @formatter:on - - // @formatter:off - private static final String UPDATE_AUTHORIZATION_SQL = "UPDATE " + TABLE_NAME - + " SET registered_client_id = ?, principal_name = ?, authorization_grant_type = ?, authorized_scopes = ?, attributes = ?, state = ?," - + " authorization_code_value = ?, authorization_code_issued_at = ?, authorization_code_expires_at = ?, authorization_code_metadata = ?," - + " access_token_value = ?, access_token_issued_at = ?, access_token_expires_at = ?, access_token_metadata = ?, access_token_type = ?, access_token_scopes = ?," - + " oidc_id_token_value = ?, oidc_id_token_issued_at = ?, oidc_id_token_expires_at = ?, oidc_id_token_metadata = ?," - + " refresh_token_value = ?, refresh_token_issued_at = ?, refresh_token_expires_at = ?, refresh_token_metadata = ?," - + " user_code_value = ?, user_code_issued_at = ?, user_code_expires_at = ?, user_code_metadata = ?," - + " device_code_value = ?, device_code_issued_at = ?, device_code_expires_at = ?, device_code_metadata = ?" - + " WHERE " + PK_FILTER; - // @formatter:on - - private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; - - private static Map columnMetadataMap; - - private final JdbcOperations jdbcOperations; - - private final LobHandler lobHandler; - - private RowMapper authorizationRowMapper; - - private Function> authorizationParametersMapper; - - /** - * Constructs a {@code JdbcOAuth2AuthorizationService} using the provided parameters. - * @param jdbcOperations the JDBC operations - * @param registeredClientRepository the registered client repository - */ - public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - this(jdbcOperations, registeredClientRepository, new DefaultLobHandler()); - } - - /** - * Constructs a {@code JdbcOAuth2AuthorizationService} using the provided parameters. - * @param jdbcOperations the JDBC operations - * @param registeredClientRepository the registered client repository - * @param lobHandler the handler for large binary fields and large text fields - */ - public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository, LobHandler lobHandler) { - Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(lobHandler, "lobHandler cannot be null"); - this.jdbcOperations = jdbcOperations; - this.lobHandler = lobHandler; - OAuth2AuthorizationRowMapper authorizationRowMapper = new OAuth2AuthorizationRowMapper( - registeredClientRepository); - authorizationRowMapper.setLobHandler(lobHandler); - this.authorizationRowMapper = authorizationRowMapper; - this.authorizationParametersMapper = new OAuth2AuthorizationParametersMapper(); - initColumnMetadata(jdbcOperations); - } - - @Override - public void save(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); - OAuth2Authorization existingAuthorization = findById(authorization.getId()); - if (existingAuthorization == null) { - insertAuthorization(authorization); - } - else { - updateAuthorization(authorization); - } - } - - private void updateAuthorization(OAuth2Authorization authorization) { - List parameters = this.authorizationParametersMapper.apply(authorization); - SqlParameterValue id = parameters.remove(0); - parameters.add(id); - try (LobCreator lobCreator = this.lobHandler.getLobCreator()) { - PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, - parameters.toArray()); - this.jdbcOperations.update(UPDATE_AUTHORIZATION_SQL, pss); - } - } - - private void insertAuthorization(OAuth2Authorization authorization) { - List parameters = this.authorizationParametersMapper.apply(authorization); - try (LobCreator lobCreator = this.lobHandler.getLobCreator()) { - PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, - parameters.toArray()); - this.jdbcOperations.update(SAVE_AUTHORIZATION_SQL, pss); - } - } - - @Override - public void remove(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, authorization.getId()) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - this.jdbcOperations.update(REMOVE_AUTHORIZATION_SQL, pss); - } - - @Nullable - @Override - public OAuth2Authorization findById(String id) { - Assert.hasText(id, "id cannot be empty"); - List parameters = new ArrayList<>(); - parameters.add(new SqlParameterValue(Types.VARCHAR, id)); - return findBy(PK_FILTER, parameters); - } - - @Nullable - @Override - public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { - Assert.hasText(token, "token cannot be empty"); - List parameters = new ArrayList<>(); - if (tokenType == null) { - parameters.add(new SqlParameterValue(Types.VARCHAR, token)); - parameters.add(mapToSqlParameter(AUTHORIZATION_CODE_VALUE, token)); - parameters.add(mapToSqlParameter(ACCESS_TOKEN_VALUE, token)); - parameters.add(mapToSqlParameter(OIDC_ID_TOKEN_VALUE, token)); - parameters.add(mapToSqlParameter(REFRESH_TOKEN_VALUE, token)); - parameters.add(mapToSqlParameter(USER_CODE_VALUE, token)); - parameters.add(mapToSqlParameter(DEVICE_CODE_VALUE, token)); - return findBy(UNKNOWN_TOKEN_TYPE_FILTER, parameters); - } - else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) { - parameters.add(new SqlParameterValue(Types.VARCHAR, token)); - return findBy(STATE_FILTER, parameters); - } - else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) { - parameters.add(mapToSqlParameter(AUTHORIZATION_CODE_VALUE, token)); - return findBy(AUTHORIZATION_CODE_FILTER, parameters); - } - else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { - parameters.add(mapToSqlParameter(ACCESS_TOKEN_VALUE, token)); - return findBy(ACCESS_TOKEN_FILTER, parameters); - } - else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) { - parameters.add(mapToSqlParameter(OIDC_ID_TOKEN_VALUE, token)); - return findBy(ID_TOKEN_FILTER, parameters); - } - else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { - parameters.add(mapToSqlParameter(REFRESH_TOKEN_VALUE, token)); - return findBy(REFRESH_TOKEN_FILTER, parameters); - } - else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) { - parameters.add(mapToSqlParameter(USER_CODE_VALUE, token)); - return findBy(USER_CODE_FILTER, parameters); - } - else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) { - parameters.add(mapToSqlParameter(DEVICE_CODE_VALUE, token)); - return findBy(DEVICE_CODE_FILTER, parameters); - } - return null; - } - - private OAuth2Authorization findBy(String filter, List parameters) { - try (LobCreator lobCreator = getLobHandler().getLobCreator()) { - PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, - parameters.toArray()); - List result = getJdbcOperations().query(LOAD_AUTHORIZATION_SQL + filter, pss, - getAuthorizationRowMapper()); - return !result.isEmpty() ? result.get(0) : null; - } - } - - /** - * Sets the {@link RowMapper} used for mapping the current row in - * {@code java.sql.ResultSet} to {@link OAuth2Authorization}. The default is - * {@link OAuth2AuthorizationRowMapper}. - * @param authorizationRowMapper the {@link RowMapper} used for mapping the current - * row in {@code ResultSet} to {@link OAuth2Authorization} - */ - public final void setAuthorizationRowMapper(RowMapper authorizationRowMapper) { - Assert.notNull(authorizationRowMapper, "authorizationRowMapper cannot be null"); - this.authorizationRowMapper = authorizationRowMapper; - } - - /** - * Sets the {@code Function} used for mapping {@link OAuth2Authorization} to a - * {@code List} of {@link SqlParameterValue}. The default is - * {@link OAuth2AuthorizationParametersMapper}. - * @param authorizationParametersMapper the {@code Function} used for mapping - * {@link OAuth2Authorization} to a {@code List} of {@link SqlParameterValue} - */ - public final void setAuthorizationParametersMapper( - Function> authorizationParametersMapper) { - Assert.notNull(authorizationParametersMapper, "authorizationParametersMapper cannot be null"); - this.authorizationParametersMapper = authorizationParametersMapper; - } - - protected final JdbcOperations getJdbcOperations() { - return this.jdbcOperations; - } - - protected final LobHandler getLobHandler() { - return this.lobHandler; - } - - protected final RowMapper getAuthorizationRowMapper() { - return this.authorizationRowMapper; - } - - protected final Function> getAuthorizationParametersMapper() { - return this.authorizationParametersMapper; - } - - private static void initColumnMetadata(JdbcOperations jdbcOperations) { - columnMetadataMap = new HashMap<>(); - ColumnMetadata columnMetadata; - - columnMetadata = getColumnMetadata(jdbcOperations, "attributes", Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, AUTHORIZATION_CODE_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, AUTHORIZATION_CODE_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, ACCESS_TOKEN_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, ACCESS_TOKEN_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, OIDC_ID_TOKEN_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, OIDC_ID_TOKEN_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, REFRESH_TOKEN_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, REFRESH_TOKEN_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, USER_CODE_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, USER_CODE_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, DEVICE_CODE_VALUE, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - columnMetadata = getColumnMetadata(jdbcOperations, DEVICE_CODE_METADATA, Types.BLOB); - columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata); - } - - private static ColumnMetadata getColumnMetadata(JdbcOperations jdbcOperations, String columnName, - int defaultDataType) { - Integer dataType = jdbcOperations.execute((ConnectionCallback) (conn) -> { - DatabaseMetaData databaseMetaData = conn.getMetaData(); - ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName); - if (rs.next()) { - return rs.getInt("DATA_TYPE"); - } - // NOTE: (Applies to HSQL) - // When a database object is created with one of the CREATE statements or - // renamed with the ALTER statement, - // if the name is enclosed in double quotes, the exact name is used as the - // case-normal form. - // But if it is not enclosed in double quotes, - // the name is converted to uppercase and this uppercase version is stored in - // the database as the case-normal form. - rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(Locale.ENGLISH), - columnName.toUpperCase(Locale.ENGLISH)); - if (rs.next()) { - return rs.getInt("DATA_TYPE"); - } - return null; - }); - return new ColumnMetadata(columnName, (dataType != null) ? dataType : defaultDataType); - } - - private static SqlParameterValue mapToSqlParameter(String columnName, String value) { - ColumnMetadata columnMetadata = columnMetadataMap.get(columnName); - return (Types.BLOB == columnMetadata.getDataType() && StringUtils.hasText(value)) - ? new SqlParameterValue(Types.BLOB, value.getBytes(StandardCharsets.UTF_8)) - : new SqlParameterValue(columnMetadata.getDataType(), value); - } - - /** - * The default {@link RowMapper} that maps the current row in - * {@code java.sql.ResultSet} to {@link OAuth2Authorization}. - */ - public static class OAuth2AuthorizationRowMapper implements RowMapper { - - private final RegisteredClientRepository registeredClientRepository; - - private LobHandler lobHandler = new DefaultLobHandler(); - - private ObjectMapper objectMapper = new ObjectMapper(); - - public OAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - this.registeredClientRepository = registeredClientRepository; - - ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Override - @SuppressWarnings("unchecked") - public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException { - String registeredClientId = rs.getString("registered_client_id"); - RegisteredClient registeredClient = this.registeredClientRepository.findById(registeredClientId); - if (registeredClient == null) { - throw new DataRetrievalFailureException("The RegisteredClient with id '" + registeredClientId - + "' was not found in the RegisteredClientRepository."); - } - - OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient); - String id = rs.getString("id"); - String principalName = rs.getString("principal_name"); - String authorizationGrantType = rs.getString("authorization_grant_type"); - Set authorizedScopes = Collections.emptySet(); - String authorizedScopesString = rs.getString("authorized_scopes"); - if (authorizedScopesString != null) { - authorizedScopes = StringUtils.commaDelimitedListToSet(authorizedScopesString); - } - Map attributes = parseMap(getLobValue(rs, "attributes")); - - builder.id(id) - .principalName(principalName) - .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType)) - .authorizedScopes(authorizedScopes) - .attributes((attrs) -> attrs.putAll(attributes)); - - String state = rs.getString("state"); - if (StringUtils.hasText(state)) { - builder.attribute(OAuth2ParameterNames.STATE, state); - } - - Instant tokenIssuedAt; - Instant tokenExpiresAt; - String authorizationCodeValue = getLobValue(rs, AUTHORIZATION_CODE_VALUE); - - if (StringUtils.hasText(authorizationCodeValue)) { - tokenIssuedAt = rs.getTimestamp("authorization_code_issued_at").toInstant(); - tokenExpiresAt = rs.getTimestamp("authorization_code_expires_at").toInstant(); - Map authorizationCodeMetadata = parseMap(getLobValue(rs, AUTHORIZATION_CODE_METADATA)); - - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(authorizationCodeValue, - tokenIssuedAt, tokenExpiresAt); - builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata)); - } - - String accessTokenValue = getLobValue(rs, ACCESS_TOKEN_VALUE); - if (StringUtils.hasText(accessTokenValue)) { - tokenIssuedAt = rs.getTimestamp("access_token_issued_at").toInstant(); - tokenExpiresAt = rs.getTimestamp("access_token_expires_at").toInstant(); - Map accessTokenMetadata = parseMap(getLobValue(rs, ACCESS_TOKEN_METADATA)); - OAuth2AccessToken.TokenType tokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) { - tokenType = OAuth2AccessToken.TokenType.BEARER; - } - else if (OAuth2AccessToken.TokenType.DPOP.getValue() - .equalsIgnoreCase(rs.getString("access_token_type"))) { - tokenType = OAuth2AccessToken.TokenType.DPOP; - } - - Set scopes = Collections.emptySet(); - String accessTokenScopes = rs.getString("access_token_scopes"); - if (accessTokenScopes != null) { - scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes); - } - OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, accessTokenValue, tokenIssuedAt, - tokenExpiresAt, scopes); - builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata)); - } - - String oidcIdTokenValue = getLobValue(rs, OIDC_ID_TOKEN_VALUE); - if (StringUtils.hasText(oidcIdTokenValue)) { - tokenIssuedAt = rs.getTimestamp("oidc_id_token_issued_at").toInstant(); - tokenExpiresAt = rs.getTimestamp("oidc_id_token_expires_at").toInstant(); - Map oidcTokenMetadata = parseMap(getLobValue(rs, OIDC_ID_TOKEN_METADATA)); - - OidcIdToken oidcToken = new OidcIdToken(oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt, - (Map) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME)); - builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata)); - } - - String refreshTokenValue = getLobValue(rs, REFRESH_TOKEN_VALUE); - if (StringUtils.hasText(refreshTokenValue)) { - tokenIssuedAt = rs.getTimestamp("refresh_token_issued_at").toInstant(); - tokenExpiresAt = null; - Timestamp refreshTokenExpiresAt = rs.getTimestamp("refresh_token_expires_at"); - if (refreshTokenExpiresAt != null) { - tokenExpiresAt = refreshTokenExpiresAt.toInstant(); - } - Map refreshTokenMetadata = parseMap(getLobValue(rs, REFRESH_TOKEN_METADATA)); - - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(refreshTokenValue, tokenIssuedAt, - tokenExpiresAt); - builder.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata)); - } - - String userCodeValue = getLobValue(rs, USER_CODE_VALUE); - if (StringUtils.hasText(userCodeValue)) { - tokenIssuedAt = rs.getTimestamp("user_code_issued_at").toInstant(); - tokenExpiresAt = rs.getTimestamp("user_code_expires_at").toInstant(); - Map userCodeMetadata = parseMap(getLobValue(rs, USER_CODE_METADATA)); - - OAuth2UserCode userCode = new OAuth2UserCode(userCodeValue, tokenIssuedAt, tokenExpiresAt); - builder.token(userCode, (metadata) -> metadata.putAll(userCodeMetadata)); - } - - String deviceCodeValue = getLobValue(rs, DEVICE_CODE_VALUE); - if (StringUtils.hasText(deviceCodeValue)) { - tokenIssuedAt = rs.getTimestamp("device_code_issued_at").toInstant(); - tokenExpiresAt = rs.getTimestamp("device_code_expires_at").toInstant(); - Map deviceCodeMetadata = parseMap(getLobValue(rs, DEVICE_CODE_METADATA)); - - OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(deviceCodeValue, tokenIssuedAt, tokenExpiresAt); - builder.token(deviceCode, (metadata) -> metadata.putAll(deviceCodeMetadata)); - } - - return builder.build(); - } - - private String getLobValue(ResultSet rs, String columnName) throws SQLException { - String columnValue = null; - ColumnMetadata columnMetadata = columnMetadataMap.get(columnName); - if (Types.BLOB == columnMetadata.getDataType()) { - byte[] columnValueBytes = this.lobHandler.getBlobAsBytes(rs, columnName); - if (columnValueBytes != null) { - columnValue = new String(columnValueBytes, StandardCharsets.UTF_8); - } - } - else if (Types.CLOB == columnMetadata.getDataType()) { - columnValue = this.lobHandler.getClobAsString(rs, columnName); - } - else { - columnValue = rs.getString(columnName); - } - return columnValue; - } - - public final void setLobHandler(LobHandler lobHandler) { - Assert.notNull(lobHandler, "lobHandler cannot be null"); - this.lobHandler = lobHandler; - } - - public final void setObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "objectMapper cannot be null"); - this.objectMapper = objectMapper; - } - - protected final RegisteredClientRepository getRegisteredClientRepository() { - return this.registeredClientRepository; - } - - protected final LobHandler getLobHandler() { - return this.lobHandler; - } - - protected final ObjectMapper getObjectMapper() { - return this.objectMapper; - } - - private Map parseMap(String data) { - try { - return this.objectMapper.readValue(data, new TypeReference<>() { - }); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - } - - /** - * The default {@code Function} that maps {@link OAuth2Authorization} to a - * {@code List} of {@link SqlParameterValue}. - */ - public static class OAuth2AuthorizationParametersMapper - implements Function> { - - private ObjectMapper objectMapper = new ObjectMapper(); - - public OAuth2AuthorizationParametersMapper() { - ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Override - public List apply(OAuth2Authorization authorization) { - List parameters = new ArrayList<>(); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getId())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getRegisteredClientId())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getPrincipalName())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue())); - - String authorizedScopes = null; - if (!CollectionUtils.isEmpty(authorization.getAuthorizedScopes())) { - authorizedScopes = StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ","); - } - parameters.add(new SqlParameterValue(Types.VARCHAR, authorizedScopes)); - - String attributes = writeMap(authorization.getAttributes()); - parameters.add(mapToSqlParameter("attributes", attributes)); - - String state = null; - String authorizationState = authorization.getAttribute(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(authorizationState)) { - state = authorizationState; - } - parameters.add(new SqlParameterValue(Types.VARCHAR, state)); - - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - List authorizationCodeSqlParameters = toSqlParameterList(AUTHORIZATION_CODE_VALUE, - AUTHORIZATION_CODE_METADATA, authorizationCode); - parameters.addAll(authorizationCodeSqlParameters); - - OAuth2Authorization.Token accessToken = authorization.getToken(OAuth2AccessToken.class); - List accessTokenSqlParameters = toSqlParameterList(ACCESS_TOKEN_VALUE, - ACCESS_TOKEN_METADATA, accessToken); - parameters.addAll(accessTokenSqlParameters); - String accessTokenType = null; - String accessTokenScopes = null; - if (accessToken != null) { - accessTokenType = accessToken.getToken().getTokenType().getValue(); - if (!CollectionUtils.isEmpty(accessToken.getToken().getScopes())) { - accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), - ","); - } - } - parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenType)); - parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes)); - - OAuth2Authorization.Token oidcIdToken = authorization.getToken(OidcIdToken.class); - List oidcIdTokenSqlParameters = toSqlParameterList(OIDC_ID_TOKEN_VALUE, - OIDC_ID_TOKEN_METADATA, oidcIdToken); - parameters.addAll(oidcIdTokenSqlParameters); - - OAuth2Authorization.Token refreshToken = authorization.getRefreshToken(); - List refreshTokenSqlParameters = toSqlParameterList(REFRESH_TOKEN_VALUE, - REFRESH_TOKEN_METADATA, refreshToken); - parameters.addAll(refreshTokenSqlParameters); - - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); - List userCodeSqlParameters = toSqlParameterList(USER_CODE_VALUE, USER_CODE_METADATA, - userCode); - parameters.addAll(userCodeSqlParameters); - - OAuth2Authorization.Token deviceCode = authorization.getToken(OAuth2DeviceCode.class); - List deviceCodeSqlParameters = toSqlParameterList(DEVICE_CODE_VALUE, - DEVICE_CODE_METADATA, deviceCode); - parameters.addAll(deviceCodeSqlParameters); - - return parameters; - } - - public final void setObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "objectMapper cannot be null"); - this.objectMapper = objectMapper; - } - - protected final ObjectMapper getObjectMapper() { - return this.objectMapper; - } - - private List toSqlParameterList(String tokenColumnName, - String tokenMetadataColumnName, OAuth2Authorization.Token token) { - - List parameters = new ArrayList<>(); - String tokenValue = null; - Timestamp tokenIssuedAt = null; - Timestamp tokenExpiresAt = null; - String metadata = null; - if (token != null) { - tokenValue = token.getToken().getTokenValue(); - if (token.getToken().getIssuedAt() != null) { - tokenIssuedAt = Timestamp.from(token.getToken().getIssuedAt()); - } - if (token.getToken().getExpiresAt() != null) { - tokenExpiresAt = Timestamp.from(token.getToken().getExpiresAt()); - } - metadata = writeMap(token.getMetadata()); - } - - parameters.add(mapToSqlParameter(tokenColumnName, tokenValue)); - parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt)); - parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt)); - parameters.add(mapToSqlParameter(tokenMetadataColumnName, metadata)); - return parameters; - } - - private String writeMap(Map data) { - try { - return this.objectMapper.writeValueAsString(data); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - } - - private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter { - - private final LobCreator lobCreator; - - private LobCreatorArgumentPreparedStatementSetter(LobCreator lobCreator, Object[] args) { - super(args); - this.lobCreator = lobCreator; - } - - @Override - protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException { - if (argValue instanceof SqlParameterValue paramValue) { - if (paramValue.getSqlType() == Types.BLOB) { - if (paramValue.getValue() != null) { - Assert.isInstanceOf(byte[].class, paramValue.getValue(), - "Value of blob parameter must be byte[]"); - } - byte[] valueBytes = (byte[]) paramValue.getValue(); - this.lobCreator.setBlobAsBytes(ps, parameterPosition, valueBytes); - return; - } - if (paramValue.getSqlType() == Types.CLOB) { - if (paramValue.getValue() != null) { - Assert.isInstanceOf(String.class, paramValue.getValue(), - "Value of clob parameter must be String"); - } - String valueString = (String) paramValue.getValue(); - this.lobCreator.setClobAsString(ps, parameterPosition, valueString); - return; - } - } - super.doSetValue(ps, parameterPosition, argValue); - } - - } - - private static final class ColumnMetadata { - - private final String columnName; - - private final int dataType; - - private ColumnMetadata(String columnName, int dataType) { - this.columnName = columnName; - this.dataType = dataType; - } - - private String getColumnName() { - return this.columnName; - } - - private int getDataType() { - return this.dataType; - } - - } - - static class JdbcOAuth2AuthorizationServiceRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - hints.resources() - .registerResource(new ClassPathResource( - "org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java deleted file mode 100644 index 5d8da42a9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.io.Serializable; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * A representation of an OAuth 2.0 Authorization, which holds state related to the - * authorization granted to a {@link #getRegisteredClientId() client}, by the - * {@link #getPrincipalName() resource owner} or itself in the case of the - * {@code client_credentials} grant type. - * - * @author Joe Grandja - * @author Krisztian Toth - * @since 0.0.1 - * @see RegisteredClient - * @see AuthorizationGrantType - * @see OAuth2Token - * @see OAuth2AccessToken - * @see OAuth2RefreshToken - */ -public class OAuth2Authorization implements Serializable { - - @Serial - private static final long serialVersionUID = 880363144799377926L; - - private String id; - - private String registeredClientId; - - private String principalName; - - private AuthorizationGrantType authorizationGrantType; - - private Set authorizedScopes; - - private Map, Token> tokens; - - private Map attributes; - - protected OAuth2Authorization() { - } - - /** - * Returns the identifier for the authorization. - * @return the identifier for the authorization - */ - public String getId() { - return this.id; - } - - /** - * Returns the identifier for the {@link RegisteredClient#getId() registered client}. - * @return the {@link RegisteredClient#getId()} - */ - public String getRegisteredClientId() { - return this.registeredClientId; - } - - /** - * Returns the {@code Principal} name of the resource owner (or client). - * @return the {@code Principal} name of the resource owner (or client) - */ - public String getPrincipalName() { - return this.principalName; - } - - /** - * Returns the {@link AuthorizationGrantType authorization grant type} used for the - * authorization. - * @return the {@link AuthorizationGrantType} used for the authorization - */ - public AuthorizationGrantType getAuthorizationGrantType() { - return this.authorizationGrantType; - } - - /** - * Returns the authorized scope(s). - * @return the {@code Set} of authorized scope(s) - * @since 0.4.0 - */ - public Set getAuthorizedScopes() { - return this.authorizedScopes; - } - - /** - * Returns the {@link Token} of type {@link OAuth2AccessToken}. - * @return the {@link Token} of type {@link OAuth2AccessToken} - */ - public Token getAccessToken() { - return getToken(OAuth2AccessToken.class); - } - - /** - * Returns the {@link Token} of type {@link OAuth2RefreshToken}. - * @return the {@link Token} of type {@link OAuth2RefreshToken}, or {@code null} if - * not available - */ - @Nullable - public Token getRefreshToken() { - return getToken(OAuth2RefreshToken.class); - } - - /** - * Returns the {@link Token} of type {@code tokenType}. - * @param tokenType the token type - * @param the type of the token - * @return the {@link Token}, or {@code null} if not available - */ - @Nullable - @SuppressWarnings("unchecked") - public Token getToken(Class tokenType) { - Assert.notNull(tokenType, "tokenType cannot be null"); - Token token = this.tokens.get(tokenType); - return (token != null) ? (Token) token : null; - } - - /** - * Returns the {@link Token} matching the {@code tokenValue}. - * @param tokenValue the token value - * @param the type of the token - * @return the {@link Token}, or {@code null} if not available - */ - @Nullable - @SuppressWarnings("unchecked") - public Token getToken(String tokenValue) { - Assert.hasText(tokenValue, "tokenValue cannot be empty"); - for (Token token : this.tokens.values()) { - if (token.getToken().getTokenValue().equals(tokenValue)) { - return (Token) token; - } - } - return null; - } - - /** - * Returns the attribute(s) associated to the authorization. - * @return a {@code Map} of the attribute(s) - */ - public Map getAttributes() { - return this.attributes; - } - - /** - * Returns the value of an attribute associated to the authorization. - * @param name the name of the attribute - * @param the type of the attribute - * @return the value of an attribute associated to the authorization, or {@code null} - * if not available - */ - @Nullable - @SuppressWarnings("unchecked") - public T getAttribute(String name) { - Assert.hasText(name, "name cannot be empty"); - return (T) this.attributes.get(name); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - OAuth2Authorization that = (OAuth2Authorization) obj; - return Objects.equals(this.id, that.id) && Objects.equals(this.registeredClientId, that.registeredClientId) - && Objects.equals(this.principalName, that.principalName) - && Objects.equals(this.authorizationGrantType, that.authorizationGrantType) - && Objects.equals(this.authorizedScopes, that.authorizedScopes) - && Objects.equals(this.tokens, that.tokens) && Objects.equals(this.attributes, that.attributes); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.registeredClientId, this.principalName, this.authorizationGrantType, - this.authorizedScopes, this.tokens, this.attributes); - } - - /** - * Returns a new {@link Builder}, initialized with the provided - * {@link RegisteredClient#getId()}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} - */ - public static Builder withRegisteredClient(RegisteredClient registeredClient) { - Assert.notNull(registeredClient, "registeredClient cannot be null"); - return new Builder(registeredClient.getId()); - } - - /** - * Returns a new {@link Builder}, initialized with the values from the provided - * {@code OAuth2Authorization}. - * @param authorization the {@code OAuth2Authorization} used for initializing the - * {@link Builder} - * @return the {@link Builder} - */ - public static Builder from(OAuth2Authorization authorization) { - Assert.notNull(authorization, "authorization cannot be null"); - return new Builder(authorization.getRegisteredClientId()).id(authorization.getId()) - .principalName(authorization.getPrincipalName()) - .authorizationGrantType(authorization.getAuthorizationGrantType()) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokens(authorization.tokens) - .attributes((attrs) -> attrs.putAll(authorization.getAttributes())); - } - - /** - * A holder of an OAuth 2.0 Token and it's associated metadata. - * - * @param the type of the {@link OAuth2Token} - * @author Joe Grandja - * @since 0.1.0 - */ - public static class Token implements Serializable { - - @Serial - private static final long serialVersionUID = -5931125502413497522L; - - protected static final String TOKEN_METADATA_NAMESPACE = "metadata.token."; - - /** - * The name of the metadata that indicates if the token has been invalidated. - */ - public static final String INVALIDATED_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("invalidated"); - - /** - * The name of the metadata used for the claims of the token. - */ - public static final String CLAIMS_METADATA_NAME = TOKEN_METADATA_NAMESPACE.concat("claims"); - - private final T token; - - private final Map metadata; - - protected Token(T token) { - this(token, defaultMetadata()); - } - - protected Token(T token, Map metadata) { - this.token = token; - this.metadata = Collections.unmodifiableMap(metadata); - } - - /** - * Returns the token of type {@link OAuth2Token}. - * @return the token of type {@link OAuth2Token} - */ - public T getToken() { - return this.token; - } - - /** - * Returns {@code true} if the token has been invalidated (e.g. revoked). The - * default is {@code false}. - * @return {@code true} if the token has been invalidated, {@code false} otherwise - */ - public boolean isInvalidated() { - return Boolean.TRUE.equals(getMetadata(INVALIDATED_METADATA_NAME)); - } - - /** - * Returns {@code true} if the token has expired. - * @return {@code true} if the token has expired, {@code false} otherwise - */ - public boolean isExpired() { - return getToken().getExpiresAt() != null && Instant.now().isAfter(getToken().getExpiresAt()); - } - - /** - * Returns {@code true} if the token is before the time it can be used. - * @return {@code true} if the token is before the time it can be used, - * {@code false} otherwise - */ - public boolean isBeforeUse() { - Instant notBefore = null; - if (!CollectionUtils.isEmpty(getClaims())) { - notBefore = (Instant) getClaims().get("nbf"); - } - return notBefore != null && Instant.now().isBefore(notBefore); - } - - /** - * Returns {@code true} if the token is currently active. - * @return {@code true} if the token is currently active, {@code false} otherwise - */ - public boolean isActive() { - return !isInvalidated() && !isExpired() && !isBeforeUse(); - } - - /** - * Returns the claims associated to the token. - * @return a {@code Map} of the claims, or {@code null} if not available - */ - @Nullable - public Map getClaims() { - return getMetadata(CLAIMS_METADATA_NAME); - } - - /** - * Returns the value of the metadata associated to the token. - * @param name the name of the metadata - * @param the value type of the metadata - * @return the value of the metadata, or {@code null} if not available - */ - @Nullable - @SuppressWarnings("unchecked") - public V getMetadata(String name) { - Assert.hasText(name, "name cannot be empty"); - return (V) this.metadata.get(name); - } - - /** - * Returns the metadata associated to the token. - * @return a {@code Map} of the metadata - */ - public Map getMetadata() { - return this.metadata; - } - - protected static Map defaultMetadata() { - Map metadata = new HashMap<>(); - metadata.put(INVALIDATED_METADATA_NAME, false); - return metadata; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - Token that = (Token) obj; - return Objects.equals(this.token, that.token) && Objects.equals(this.metadata, that.metadata); - } - - @Override - public int hashCode() { - return Objects.hash(this.token, this.metadata); - } - - } - - /** - * A builder for {@link OAuth2Authorization}. - */ - public static class Builder { - - private String id; - - private final String registeredClientId; - - private String principalName; - - private AuthorizationGrantType authorizationGrantType; - - private Set authorizedScopes; - - private Map, Token> tokens = new HashMap<>(); - - private final Map attributes = new HashMap<>(); - - protected Builder(String registeredClientId) { - this.registeredClientId = registeredClientId; - } - - /** - * Sets the identifier for the authorization. - * @param id the identifier for the authorization - * @return the {@link Builder} - */ - public Builder id(String id) { - this.id = id; - return this; - } - - /** - * Sets the {@code Principal} name of the resource owner (or client). - * @param principalName the {@code Principal} name of the resource owner (or - * client) - * @return the {@link Builder} - */ - public Builder principalName(String principalName) { - this.principalName = principalName; - return this; - } - - /** - * Sets the {@link AuthorizationGrantType authorization grant type} used for the - * authorization. - * @param authorizationGrantType the {@link AuthorizationGrantType} - * @return the {@link Builder} - */ - public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) { - this.authorizationGrantType = authorizationGrantType; - return this; - } - - /** - * Sets the authorized scope(s). - * @param authorizedScopes the {@code Set} of authorized scope(s) - * @return the {@link Builder} - * @since 0.4.0 - */ - public Builder authorizedScopes(Set authorizedScopes) { - this.authorizedScopes = authorizedScopes; - return this; - } - - /** - * Sets the {@link OAuth2AccessToken access token}. - * @param accessToken the {@link OAuth2AccessToken} - * @return the {@link Builder} - */ - public Builder accessToken(OAuth2AccessToken accessToken) { - return token(accessToken); - } - - /** - * Sets the {@link OAuth2RefreshToken refresh token}. - * @param refreshToken the {@link OAuth2RefreshToken} - * @return the {@link Builder} - */ - public Builder refreshToken(OAuth2RefreshToken refreshToken) { - return token(refreshToken); - } - - /** - * Sets the {@link OAuth2Token token}. - * @param token the token - * @param the type of the token - * @return the {@link Builder} - */ - public Builder token(T token) { - return token(token, (metadata) -> { - }); - } - - /** - * Sets the {@link OAuth2Token token} and associated metadata. - * @param token the token - * @param metadataConsumer a {@code Consumer} of the metadata {@code Map} - * @param the type of the token - * @return the {@link Builder} - */ - public Builder token(T token, Consumer> metadataConsumer) { - Assert.notNull(token, "token cannot be null"); - Map metadata = Token.defaultMetadata(); - Token existingToken = this.tokens.get(token.getClass()); - if (existingToken != null) { - metadata.putAll(existingToken.getMetadata()); - } - metadataConsumer.accept(metadata); - Class tokenClass = token.getClass(); - this.tokens.put(tokenClass, new Token<>(token, metadata)); - return this; - } - - /** - * Invalidates the {@link OAuth2Token token}. - * @param token the token - * @param the type of the token - * @return the {@link Builder} - * @since 1.4 - */ - public Builder invalidate(T token) { - Assert.notNull(token, "token cannot be null"); - if (this.tokens.get(token.getClass()) == null) { - return this; - } - token(token, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); - if (OAuth2RefreshToken.class.isAssignableFrom(token.getClass())) { - Token accessToken = this.tokens.get(OAuth2AccessToken.class); - token(accessToken.getToken(), - (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); - - Token authorizationCode = this.tokens.get(OAuth2AuthorizationCode.class); - if (authorizationCode != null && !authorizationCode.isInvalidated()) { - token(authorizationCode.getToken(), - (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); - } - } - return this; - } - - protected final Builder tokens(Map, Token> tokens) { - this.tokens = new HashMap<>(tokens); - return this; - } - - /** - * Adds an attribute associated to the authorization. - * @param name the name of the attribute - * @param value the value of the attribute - * @return the {@link Builder} - */ - public Builder attribute(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.attributes.put(name, value); - return this; - } - - /** - * A {@code Consumer} of the attributes {@code Map} allowing the ability to add, - * replace, or remove. - * @param attributesConsumer a {@link Consumer} of the attributes {@code Map} - * @return the {@link Builder} - */ - public Builder attributes(Consumer> attributesConsumer) { - attributesConsumer.accept(this.attributes); - return this; - } - - /** - * Builds a new {@link OAuth2Authorization}. - * @return the {@link OAuth2Authorization} - */ - public OAuth2Authorization build() { - Assert.hasText(this.principalName, "principalName cannot be empty"); - Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null"); - - OAuth2Authorization authorization = new OAuth2Authorization(); - if (!StringUtils.hasText(this.id)) { - this.id = UUID.randomUUID().toString(); - } - authorization.id = this.id; - authorization.registeredClientId = this.registeredClientId; - authorization.principalName = this.principalName; - authorization.authorizationGrantType = this.authorizationGrantType; - authorization.authorizedScopes = Collections.unmodifiableSet(!CollectionUtils.isEmpty(this.authorizedScopes) - ? new HashSet<>(this.authorizedScopes) : new HashSet<>()); - authorization.tokens = Collections.unmodifiableMap(this.tokens); - authorization.attributes = Collections.unmodifiableMap(this.attributes); - return authorization; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationCode.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationCode.java deleted file mode 100644 index 4447962b1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationCode.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import java.time.Instant; - -import org.springframework.security.oauth2.core.AbstractOAuth2Token; - -/** - * An implementation of an {@link AbstractOAuth2Token} representing an OAuth 2.0 - * Authorization Code Grant. - * - * @author Joe Grandja - * @since 0.0.3 - * @see AbstractOAuth2Token - * @see Section - * 4.1 Authorization Code Grant - */ -public class OAuth2AuthorizationCode extends AbstractOAuth2Token { - - /** - * Constructs an {@code OAuth2AuthorizationCode} using the provided parameters. - * @param tokenValue the token value - * @param issuedAt the time at which the token was issued - * @param expiresAt the time at which the token expires - */ - public OAuth2AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt) { - super(tokenValue, issuedAt, expiresAt); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java deleted file mode 100644 index e83b05236..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; - -import org.springframework.lang.NonNull; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * A representation of an OAuth 2.0 "consent" to an Authorization request, which holds - * state related to the set of {@link #getAuthorities() authorities} granted to a - * {@link #getRegisteredClientId() client} by the {@link #getPrincipalName() resource - * owner}. - *

- * When authorizing access for a given client, the resource owner may only grant a subset - * of the authorities the client requested. The typical use-case is the - * {@code authorization_code} flow, in which the client requests a set of {@code scope}s. - * The resource owner then selects which scopes they grant to the client. - * - * @author Daniel Garnier-Moiroux - * @since 0.1.2 - */ -public final class OAuth2AuthorizationConsent implements Serializable { - - @Serial - private static final long serialVersionUID = -1950648027021276018L; - - private static final String AUTHORITIES_SCOPE_PREFIX = "SCOPE_"; - - private final String registeredClientId; - - private final String principalName; - - private final Set authorities; - - private OAuth2AuthorizationConsent(String registeredClientId, String principalName, - Set authorities) { - this.registeredClientId = registeredClientId; - this.principalName = principalName; - this.authorities = Collections.unmodifiableSet(authorities); - } - - /** - * Returns the identifier for the {@link RegisteredClient#getId() registered client}. - * @return the {@link RegisteredClient#getId()} - */ - public String getRegisteredClientId() { - return this.registeredClientId; - } - - /** - * Returns the {@code Principal} name of the resource owner (or client). - * @return the {@code Principal} name of the resource owner (or client) - */ - public String getPrincipalName() { - return this.principalName; - } - - /** - * Returns the {@link GrantedAuthority authorities} granted to the client by the - * principal. - * @return the {@link GrantedAuthority authorities} granted to the client by the - * principal. - */ - public Set getAuthorities() { - return this.authorities; - } - - /** - * Convenience method for obtaining the {@code scope}s granted to the client by the - * principal, extracted from the {@link #getAuthorities() authorities}. - * @return the {@code scope}s granted to the client by the principal. - */ - public Set getScopes() { - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : getAuthorities()) { - if (authority.getAuthority().startsWith(AUTHORITIES_SCOPE_PREFIX)) { - authorities.add(authority.getAuthority().substring(AUTHORITIES_SCOPE_PREFIX.length())); - } - } - return authorities; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - OAuth2AuthorizationConsent that = (OAuth2AuthorizationConsent) obj; - return Objects.equals(this.registeredClientId, that.registeredClientId) - && Objects.equals(this.principalName, that.principalName) - && Objects.equals(this.authorities, that.authorities); - } - - @Override - public int hashCode() { - return Objects.hash(this.registeredClientId, this.principalName, this.authorities); - } - - /** - * Returns a new {@link Builder}, initialized with the values from the provided - * {@code OAuth2AuthorizationConsent}. - * @param authorizationConsent the {@code OAuth2AuthorizationConsent} used for - * initializing the {@link Builder} - * @return the {@link Builder} - */ - public static Builder from(OAuth2AuthorizationConsent authorizationConsent) { - Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); - return new Builder(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName(), - authorizationConsent.getAuthorities()); - } - - /** - * Returns a new {@link Builder}, initialized with the given - * {@link RegisteredClient#getClientId() registeredClientId} and {@code Principal} - * name. - * @param registeredClientId the {@link RegisteredClient#getId()} - * @param principalName the {@code Principal} name - * @return the {@link Builder} - */ - public static Builder withId(@NonNull String registeredClientId, @NonNull String principalName) { - Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); - Assert.hasText(principalName, "principalName cannot be empty"); - return new Builder(registeredClientId, principalName); - } - - /** - * A builder for {@link OAuth2AuthorizationConsent}. - */ - public static final class Builder { - - private final String registeredClientId; - - private final String principalName; - - private final Set authorities = new HashSet<>(); - - private Builder(String registeredClientId, String principalName) { - this(registeredClientId, principalName, Collections.emptySet()); - } - - private Builder(String registeredClientId, String principalName, Set authorities) { - this.registeredClientId = registeredClientId; - this.principalName = principalName; - if (!CollectionUtils.isEmpty(authorities)) { - this.authorities.addAll(authorities); - } - } - - /** - * Adds a scope to the collection of {@code authorities} in the resulting - * {@link OAuth2AuthorizationConsent}, wrapping it in a - * {@link SimpleGrantedAuthority}, prefixed by {@code SCOPE_}. For example, a - * {@code message.write} scope would be stored as {@code SCOPE_message.write}. - * @param scope the scope - * @return the {@code Builder} for further configuration - */ - public Builder scope(String scope) { - authority(new SimpleGrantedAuthority(AUTHORITIES_SCOPE_PREFIX + scope)); - return this; - } - - /** - * Adds a {@link GrantedAuthority} to the collection of {@code authorities} in the - * resulting {@link OAuth2AuthorizationConsent}. - * @param authority the {@link GrantedAuthority} - * @return the {@code Builder} for further configuration - */ - public Builder authority(GrantedAuthority authority) { - this.authorities.add(authority); - return this; - } - - /** - * A {@code Consumer} of the {@code authorities}, allowing the ability to add, - * replace or remove. - * @param authoritiesConsumer a {@code Consumer} of the {@code authorities} - * @return the {@code Builder} for further configuration - */ - public Builder authorities(Consumer> authoritiesConsumer) { - authoritiesConsumer.accept(this.authorities); - return this; - } - - /** - * Validate the authorities and build the {@link OAuth2AuthorizationConsent}. - * There must be at least one {@link GrantedAuthority}. - * @return the {@link OAuth2AuthorizationConsent} - */ - public OAuth2AuthorizationConsent build() { - Assert.notEmpty(this.authorities, "authorities cannot be empty"); - return new OAuth2AuthorizationConsent(this.registeredClientId, this.principalName, this.authorities); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java deleted file mode 100644 index 56d8b2498..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization; - -import java.security.Principal; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; - -/** - * Implementations of this interface are responsible for the management of - * {@link OAuth2AuthorizationConsent OAuth 2.0 Authorization Consent(s)}. - * - * @author Daniel Garnier-Moiroux - * @since 0.1.2 - * @see OAuth2AuthorizationConsent - */ -public interface OAuth2AuthorizationConsentService { - - /** - * Saves the {@link OAuth2AuthorizationConsent}. - * @param authorizationConsent the {@link OAuth2AuthorizationConsent} - */ - void save(OAuth2AuthorizationConsent authorizationConsent); - - /** - * Removes the {@link OAuth2AuthorizationConsent}. - * @param authorizationConsent the {@link OAuth2AuthorizationConsent} - */ - void remove(OAuth2AuthorizationConsent authorizationConsent); - - /** - * Returns the {@link OAuth2AuthorizationConsent} identified by the provided - * {@code registeredClientId} and {@code principalName}, or {@code null} if not found. - * @param registeredClientId the identifier for the {@link RegisteredClient} - * @param principalName the name of the {@link Principal} - * @return the {@link OAuth2AuthorizationConsent} if found, otherwise {@code null} - */ - @Nullable - OAuth2AuthorizationConsent findById(String registeredClientId, String principalName); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadata.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadata.java deleted file mode 100644 index 7dd179728..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadata.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.util.Map; - -import org.springframework.util.Assert; - -/** - * A representation of an OAuth 2.0 Authorization Server Metadata response, which is - * returned from an OAuth 2.0 Authorization Server's Metadata Endpoint, and contains a set - * of claims about the Authorization Server's configuration. The claims are defined by the - * OAuth 2.0 Authorization Server Metadata specification (RFC 8414). - * - * @author Daniel Garnier-Moiroux - * @since 0.1.1 - * @see AbstractOAuth2AuthorizationServerMetadata - * @see 3.2. - * Authorization Server Metadata Response - */ -public final class OAuth2AuthorizationServerMetadata extends AbstractOAuth2AuthorizationServerMetadata { - - @Serial - private static final long serialVersionUID = 3993358339217009284L; - - private OAuth2AuthorizationServerMetadata(Map claims) { - super(claims); - } - - /** - * Constructs a new {@link Builder} with empty claims. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Constructs a new {@link Builder} with the provided claims. - * @param claims the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder withClaims(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - return new Builder().claims((c) -> c.putAll(claims)); - } - - /** - * Helps configure an {@link OAuth2AuthorizationServerMetadata}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Validate the claims and build the {@link OAuth2AuthorizationServerMetadata}. - *

- * The following claims are REQUIRED: {@code issuer}, - * {@code authorization_endpoint}, {@code token_endpoint} and - * {@code response_types_supported}. - * @return the {@link OAuth2AuthorizationServerMetadata} - */ - @Override - public OAuth2AuthorizationServerMetadata build() { - validate(); - return new OAuth2AuthorizationServerMetadata(getClaims()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java deleted file mode 100644 index ba88f8b53..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.net.URL; -import java.util.List; - -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; - -/** - * A {@link ClaimAccessor} for the "claims" an Authorization Server describes about its - * configuration, used in OAuth 2.0 Authorization Server Metadata and OpenID Connect - * Discovery 1.0. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.1 - * @see ClaimAccessor - * @see OAuth2AuthorizationServerMetadataClaimNames - * @see 2. - * Authorization Server Metadata - * @see 3. OpenID - * Provider Metadata - * @see 4. - * Device Authorization Grant Metadata - * @see 3.3 Mutual-TLS Client - * Certificate-Bound Access Tokens Metadata - * @see 5.1 OAuth 2.0 Demonstrating - * Proof of Possession (DPoP) Metadata - * @see 5. - * OAuth 2.0 Pushed Authorization Requests Metadata - */ -public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor { - - /** - * Returns the {@code URL} the Authorization Server asserts as its Issuer Identifier - * {@code (issuer)}. - * @return the {@code URL} the Authorization Server asserts as its Issuer Identifier - */ - default URL getIssuer() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.ISSUER); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Authorization Endpoint - * {@code (authorization_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Authorization Endpoint - */ - default URL getAuthorizationEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Pushed Authorization Request Endpoint - * {@code (pushed_authorization_request_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Pushed Authorization Request Endpoint - * @since 1.5 - */ - default URL getPushedAuthorizationRequestEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Device Authorization Endpoint - * {@code (device_authorization_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Device Authorization Endpoint - * @since 1.1 - */ - default URL getDeviceAuthorizationEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Token Endpoint - */ - default URL getTokenEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT); - } - - /** - * Returns the client authentication methods supported by the OAuth 2.0 Token Endpoint - * {@code (token_endpoint_auth_methods_supported)}. - * @return the client authentication methods supported by the OAuth 2.0 Token Endpoint - */ - default List getTokenEndpointAuthenticationMethods() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED); - } - - /** - * Returns the {@code URL} of the JSON Web Key Set {@code (jwks_uri)}. - * @return the {@code URL} of the JSON Web Key Set - */ - default URL getJwkSetUrl() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI); - } - - /** - * Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}. - * @return the OAuth 2.0 {@code scope} values supported - */ - default List getScopes() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED); - } - - /** - * Returns the OAuth 2.0 {@code response_type} values supported - * {@code (response_types_supported)}. - * @return the OAuth 2.0 {@code response_type} values supported - */ - default List getResponseTypes() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED); - } - - /** - * Returns the OAuth 2.0 {@code grant_type} values supported - * {@code (grant_types_supported)}. - * @return the OAuth 2.0 {@code grant_type} values supported - */ - default List getGrantTypes() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Token Revocation Endpoint - * {@code (revocation_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Token Revocation Endpoint - */ - default URL getTokenRevocationEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT); - } - - /** - * Returns the client authentication methods supported by the OAuth 2.0 Token - * Revocation Endpoint {@code (revocation_endpoint_auth_methods_supported)}. - * @return the client authentication methods supported by the OAuth 2.0 Token - * Revocation Endpoint - */ - default List getTokenRevocationEndpointAuthenticationMethods() { - return getClaimAsStringList( - OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Token Introspection Endpoint - * {@code (introspection_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Token Introspection Endpoint - */ - default URL getTokenIntrospectionEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT); - } - - /** - * Returns the client authentication methods supported by the OAuth 2.0 Token - * Introspection Endpoint {@code (introspection_endpoint_auth_methods_supported)}. - * @return the client authentication methods supported by the OAuth 2.0 Token - * Introspection Endpoint - */ - default List getTokenIntrospectionEndpointAuthenticationMethods() { - return getClaimAsStringList( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED); - } - - /** - * Returns the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint - * {@code (registration_endpoint)}. - * @return the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint - * @since 0.4.0 - */ - default URL getClientRegistrationEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT); - } - - /** - * Returns the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values - * supported {@code (code_challenge_methods_supported)}. - * @return the {@code code_challenge_method} values supported - */ - default List getCodeChallengeMethods() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED); - } - - /** - * Returns {@code true} to indicate support for mutual-TLS client certificate-bound - * access tokens {@code (tls_client_certificate_bound_access_tokens)}. - * @return {@code true} to indicate support for mutual-TLS client certificate-bound - * access tokens, {@code false} otherwise - * @since 1.3 - */ - default boolean isTlsClientCertificateBoundAccessTokens() { - return Boolean.TRUE.equals(getClaimAsBoolean( - OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS)); - } - - /** - * Returns the {@link JwsAlgorithms JSON Web Signature (JWS) algorithms} supported for - * DPoP Proof JWTs {@code (dpop_signing_alg_values_supported)}. - * @return the {@link JwsAlgorithms JSON Web Signature (JWS) algorithms} supported for - * DPoP Proof JWTs - * @since 1.5 - */ - default List getDPoPSigningAlgorithms() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java deleted file mode 100644 index c8ee980ee..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; - -/** - * The names of the "claims" an Authorization Server describes about its configuration, - * used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.1 - * @see 2. - * Authorization Server Metadata - * @see 3. OpenID - * Provider Metadata - * @see 4. - * Device Authorization Grant Metadata - * @see 3.3 Mutual-TLS Client - * Certificate-Bound Access Tokens Metadata - * @see 5.1 OAuth 2.0 Demonstrating - * Proof of Possession (DPoP) Metadata - * @see 5. - * OAuth 2.0 Pushed Authorization Requests Metadata - */ -public class OAuth2AuthorizationServerMetadataClaimNames { - - /** - * {@code issuer} - the {@code URL} the Authorization Server asserts as its Issuer - * Identifier - */ - public static final String ISSUER = "issuer"; - - /** - * {@code authorization_endpoint} - the {@code URL} of the OAuth 2.0 Authorization - * Endpoint - */ - public static final String AUTHORIZATION_ENDPOINT = "authorization_endpoint"; - - /** - * {@code pushed_authorization_request_endpoint} - the {@code URL} of the OAuth 2.0 - * Pushed Authorization Request Endpoint - * @since 1.5 - */ - public static final String PUSHED_AUTHORIZATION_REQUEST_ENDPOINT = "pushed_authorization_request_endpoint"; - - /** - * {@code device_authorization_endpoint} - the {@code URL} of the OAuth 2.0 Device - * Authorization Endpoint - * @since 1.1 - */ - public static final String DEVICE_AUTHORIZATION_ENDPOINT = "device_authorization_endpoint"; - - /** - * {@code token_endpoint} - the {@code URL} of the OAuth 2.0 Token Endpoint - */ - public static final String TOKEN_ENDPOINT = "token_endpoint"; - - /** - * {@code token_endpoint_auth_methods_supported} - the client authentication methods - * supported by the OAuth 2.0 Token Endpoint - */ - public static final String TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED = "token_endpoint_auth_methods_supported"; - - /** - * {@code jwks_uri} - the {@code URL} of the JSON Web Key Set - */ - public static final String JWKS_URI = "jwks_uri"; - - /** - * {@code scopes_supported} - the OAuth 2.0 {@code scope} values supported - */ - public static final String SCOPES_SUPPORTED = "scopes_supported"; - - /** - * {@code response_types_supported} - the OAuth 2.0 {@code response_type} values - * supported - */ - public static final String RESPONSE_TYPES_SUPPORTED = "response_types_supported"; - - /** - * {@code grant_types_supported} - the OAuth 2.0 {@code grant_type} values supported - */ - public static final String GRANT_TYPES_SUPPORTED = "grant_types_supported"; - - /** - * {@code revocation_endpoint} - the {@code URL} of the OAuth 2.0 Token Revocation - * Endpoint - */ - public static final String REVOCATION_ENDPOINT = "revocation_endpoint"; - - /** - * {@code revocation_endpoint_auth_methods_supported} - the client authentication - * methods supported by the OAuth 2.0 Token Revocation Endpoint - */ - public static final String REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED = "revocation_endpoint_auth_methods_supported"; - - /** - * {@code introspection_endpoint} - the {@code URL} of the OAuth 2.0 Token - * Introspection Endpoint - */ - public static final String INTROSPECTION_ENDPOINT = "introspection_endpoint"; - - /** - * {@code introspection_endpoint_auth_methods_supported} - the client authentication - * methods supported by the OAuth 2.0 Token Introspection Endpoint - */ - public static final String INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED = "introspection_endpoint_auth_methods_supported"; - - /** - * {@code registration_endpoint} - the {@code URL} of the OAuth 2.0 Dynamic Client - * Registration Endpoint - * @since 0.4.0 - */ - public static final String REGISTRATION_ENDPOINT = "registration_endpoint"; - - /** - * {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) - * {@code code_challenge_method} values supported - */ - public static final String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported"; - - /** - * {@code tls_client_certificate_bound_access_tokens} - {@code true} to indicate - * support for mutual-TLS client certificate-bound access tokens - * @since 1.3 - */ - public static final String TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS = "tls_client_certificate_bound_access_tokens"; - - /** - * {@code dpop_signing_alg_values_supported} - the {@link JwsAlgorithms JSON Web - * Signature (JWS) algorithms} supported for DPoP Proof JWTs - * @since 1.5 - */ - public static final String DPOP_SIGNING_ALG_VALUES_SUPPORTED = "dpop_signing_alg_values_supported"; - - protected OAuth2AuthorizationServerMetadataClaimNames() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java deleted file mode 100644 index 6e5ed530e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import org.springframework.lang.Nullable; - -/** - * Implementations of this interface are responsible for the management of - * {@link OAuth2Authorization OAuth 2.0 Authorization(s)}. - * - * @author Joe Grandja - * @since 0.0.1 - * @see OAuth2Authorization - * @see OAuth2TokenType - */ -public interface OAuth2AuthorizationService { - - /** - * Saves the {@link OAuth2Authorization}. - * @param authorization the {@link OAuth2Authorization} - */ - void save(OAuth2Authorization authorization); - - /** - * Removes the {@link OAuth2Authorization}. - * @param authorization the {@link OAuth2Authorization} - */ - void remove(OAuth2Authorization authorization); - - /** - * Returns the {@link OAuth2Authorization} identified by the provided {@code id}, or - * {@code null} if not found. - * @param id the authorization identifier - * @return the {@link OAuth2Authorization} if found, otherwise {@code null} - */ - @Nullable - OAuth2Authorization findById(String id); - - /** - * Returns the {@link OAuth2Authorization} containing the provided {@code token}, or - * {@code null} if not found. - * @param token the token credential - * @param tokenType the {@link OAuth2TokenType token type} - * @return the {@link OAuth2Authorization} if found, otherwise {@code null} - */ - @Nullable - OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenIntrospection.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenIntrospection.java deleted file mode 100644 index 660604e43..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenIntrospection.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.io.Serializable; -import java.net.URI; -import java.net.URL; -import java.time.Instant; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.util.Assert; - -/** - * A representation of the claims returned in an OAuth 2.0 Token Introspection Response. - * - * @author Gerardo Roza - * @author Joe Grandja - * @since 0.1.1 - * @see OAuth2TokenIntrospectionClaimAccessor - * @see Section - * 2.2 Introspection Response - */ -public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionClaimAccessor, Serializable { - - @Serial - private static final long serialVersionUID = -8846164058150912395L; - - private final Map claims; - - private OAuth2TokenIntrospection(Map claims) { - this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims)); - } - - /** - * Returns the claims in the Token Introspection Response. - * @return a {@code Map} of the claims - */ - @Override - public Map getClaims() { - return this.claims; - } - - /** - * Constructs a new {@link Builder} initialized with the {@link #isActive() active} - * claim to {@code false}. - * @return the {@link Builder} - */ - public static Builder builder() { - return builder(false); - } - - /** - * Constructs a new {@link Builder} initialized with the provided {@link #isActive() - * active} claim. - * @param active {@code true} if the token is currently active, {@code false} - * otherwise - * @return the {@link Builder} - */ - public static Builder builder(boolean active) { - return new Builder(active); - } - - /** - * Constructs a new {@link Builder} initialized with the provided claims. - * @param claims the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder withClaims(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - return builder().claims((c) -> c.putAll(claims)); - } - - /** - * A builder for {@link OAuth2TokenIntrospection}. - */ - public static final class Builder { - - private final Map claims = new LinkedHashMap<>(); - - private Builder(boolean active) { - active(active); - } - - /** - * Sets the indicator of whether or not the presented token is currently active, - * REQUIRED. - * @param active {@code true} if the token is currently active, {@code false} - * otherwise - * @return the {@link Builder} for further configuration - */ - public Builder active(boolean active) { - return claim(OAuth2TokenIntrospectionClaimNames.ACTIVE, active); - } - - /** - * Add the scope associated with this token, OPTIONAL. - * @param scope the scope associated with this token - * @return the {@link Builder} for further configuration - */ - public Builder scope(String scope) { - addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.SCOPE, scope); - return this; - } - - /** - * A {@code Consumer} of the scope(s) associated with this token, allowing the - * ability to add, replace, or remove, OPTIONAL. - * @param scopesConsumer a {@code Consumer} of the scope(s) associated with this - * token - * @return the {@link Builder} for further configuration - */ - public Builder scopes(Consumer> scopesConsumer) { - acceptClaimValues(OAuth2TokenIntrospectionClaimNames.SCOPE, scopesConsumer); - return this; - } - - /** - * Sets the client identifier for the OAuth 2.0 client that requested this token, - * OPTIONAL. - * @param clientId the client identifier for the OAuth 2.0 client that requested - * this token - * @return the {@link Builder} for further configuration - */ - public Builder clientId(String clientId) { - return claim(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, clientId); - } - - /** - * Sets the human-readable identifier for the resource owner who authorized this - * token, OPTIONAL. - * @param username the human-readable identifier for the resource owner who - * authorized this token - * @return the {@link Builder} for further configuration - */ - public Builder username(String username) { - return claim(OAuth2TokenIntrospectionClaimNames.USERNAME, username); - } - - /** - * Sets the token type (e.g. bearer), OPTIONAL. - * @param tokenType the token type - * @return the {@link Builder} for further configuration - */ - public Builder tokenType(String tokenType) { - return claim(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, tokenType); - } - - /** - * Sets the time indicating when this token will expire, OPTIONAL. - * @param expiresAt the time indicating when this token will expire - * @return the {@link Builder} for further configuration - */ - public Builder expiresAt(Instant expiresAt) { - return claim(OAuth2TokenIntrospectionClaimNames.EXP, expiresAt); - } - - /** - * Sets the time indicating when this token was originally issued, OPTIONAL. - * @param issuedAt the time indicating when this token was originally issued - * @return the {@link Builder} for further configuration - */ - public Builder issuedAt(Instant issuedAt) { - return claim(OAuth2TokenIntrospectionClaimNames.IAT, issuedAt); - } - - /** - * Sets the time indicating when this token is not to be used before, OPTIONAL. - * @param notBefore the time indicating when this token is not to be used before - * @return the {@link Builder} for further configuration - */ - public Builder notBefore(Instant notBefore) { - return claim(OAuth2TokenIntrospectionClaimNames.NBF, notBefore); - } - - /** - * Sets the subject of the token, usually a machine-readable identifier of the - * resource owner who authorized this token, OPTIONAL. - * @param subject the subject of the token - * @return the {@link Builder} for further configuration - */ - public Builder subject(String subject) { - return claim(OAuth2TokenIntrospectionClaimNames.SUB, subject); - } - - /** - * Add the identifier representing the intended audience for this token, OPTIONAL. - * @param audience the identifier representing the intended audience for this - * token - * @return the {@link Builder} for further configuration - */ - public Builder audience(String audience) { - addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.AUD, audience); - return this; - } - - /** - * A {@code Consumer} of the intended audience(s) for this token, allowing the - * ability to add, replace, or remove, OPTIONAL. - * @param audiencesConsumer a {@code Consumer} of the intended audience(s) for - * this token - * @return the {@link Builder} for further configuration - */ - public Builder audiences(Consumer> audiencesConsumer) { - acceptClaimValues(OAuth2TokenIntrospectionClaimNames.AUD, audiencesConsumer); - return this; - } - - /** - * Sets the issuer of this token, OPTIONAL. - * @param issuer the issuer of this token - * @return the {@link Builder} for further configuration - */ - public Builder issuer(String issuer) { - return claim(OAuth2TokenIntrospectionClaimNames.ISS, issuer); - } - - /** - * Sets the identifier for the token, OPTIONAL. - * @param jti the identifier for the token - * @return the {@link Builder} for further configuration - */ - public Builder id(String jti) { - return claim(OAuth2TokenIntrospectionClaimNames.JTI, jti); - } - - /** - * Sets the claim. - * @param name the claim name - * @param value the claim value - * @return the {@link Builder} for further configuration - */ - public Builder claim(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.put(name, value); - return this; - } - - /** - * Provides access to every {@link #claim(String, Object)} declared so far with - * the possibility to add, replace, or remove. - * @param claimsConsumer a {@code Consumer} of the claims - * @return the {@link Builder} for further configurations - */ - public Builder claims(Consumer> claimsConsumer) { - claimsConsumer.accept(this.claims); - return this; - } - - /** - * Validate the claims and build the {@link OAuth2TokenIntrospection}. - *

- * The following claims are REQUIRED: {@code active} - * @return the {@link OAuth2TokenIntrospection} - */ - public OAuth2TokenIntrospection build() { - validate(); - return new OAuth2TokenIntrospection(this.claims); - } - - private void validate() { - Assert.notNull(this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), "active cannot be null"); - Assert.isInstanceOf(Boolean.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), - "active must be of type boolean"); - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.SCOPE)) { - Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.SCOPE), - "scope must be of type List"); - } - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.EXP)) { - Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.EXP), - "exp must be of type Instant"); - } - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.IAT)) { - Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.IAT), - "iat must be of type Instant"); - } - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.NBF)) { - Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.NBF), - "nbf must be of type Instant"); - } - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.AUD)) { - Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.AUD), - "aud must be of type List"); - } - if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.ISS)) { - validateURL(this.claims.get(OAuth2TokenIntrospectionClaimNames.ISS), "iss must be a valid URL"); - } - } - - @SuppressWarnings("unchecked") - private void addClaimToClaimList(String name, String value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.computeIfAbsent(name, (k) -> new LinkedList()); - ((List) this.claims.get(name)).add(value); - } - - @SuppressWarnings("unchecked") - private void acceptClaimValues(String name, Consumer> valuesConsumer) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); - this.claims.computeIfAbsent(name, (k) -> new LinkedList()); - List values = (List) this.claims.get(name); - valuesConsumer.accept(values); - } - - private static void validateURL(Object url, String errorMessage) { - if (URL.class.isAssignableFrom(url.getClass())) { - return; - } - - try { - new URI(url.toString()).toURL(); - } - catch (Exception ex) { - throw new IllegalArgumentException(errorMessage, ex); - } - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenType.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenType.java deleted file mode 100644 index 6b97f8e78..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenType.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.io.Serial; -import java.io.Serializable; - -import org.springframework.util.Assert; - -/** - * Standard token types defined in the OAuth Token Type Hints Registry. - * - * @author Joe Grandja - * @since 0.0.1 - * @see 4.1.2 - * OAuth Token Type Hints Registry - */ -public final class OAuth2TokenType implements Serializable { - - @Serial - private static final long serialVersionUID = -9015673781220922768L; - - /** - * {@code access_token} token type. - */ - public static final OAuth2TokenType ACCESS_TOKEN = new OAuth2TokenType("access_token"); - - /** - * {@code refresh_token} token type. - */ - public static final OAuth2TokenType REFRESH_TOKEN = new OAuth2TokenType("refresh_token"); - - private final String value; - - /** - * Constructs an {@code OAuth2TokenType} using the provided value. - * @param value the value of the token type - */ - public OAuth2TokenType(String value) { - Assert.hasText(value, "value cannot be empty"); - this.value = value; - } - - /** - * Returns the value of the token type. - * @return the value of the token type - */ - public String getValue() { - return this.value; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - OAuth2TokenType that = (OAuth2TokenType) obj; - return getValue().equals(that.getValue()); - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java deleted file mode 100644 index 2ddd71a7c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.aot.hint; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; - -import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.BindingReflectionHintsRegistrar; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; -import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; -import org.springframework.beans.factory.aot.BeanRegistrationCode; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.jackson2.CoreJackson2Module; -import org.springframework.security.oauth2.core.AbstractOAuth2Token; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.security.web.jackson2.WebServletJackson2Module; -import org.springframework.security.web.savedrequest.DefaultSavedRequest; -import org.springframework.util.ClassUtils; - -/** - * {@link BeanRegistrationAotProcessor} that detects specific registered beans and - * contributes the required {@link RuntimeHints}. Statically registered via - * META-INF/spring/aot.factories. - * - * @author Joe Grandja - * @author Josh Long - * @author William Koch - * @since 1.2 - */ -class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { - - private boolean jackson2Contributed; - - @Override - public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { - boolean isJdbcBasedOAuth2AuthorizationService = JdbcOAuth2AuthorizationService.class - .isAssignableFrom(registeredBean.getBeanClass()); - - boolean isJdbcBasedRegisteredClientRepository = JdbcRegisteredClientRepository.class - .isAssignableFrom(registeredBean.getBeanClass()); - - // @formatter:off - if ((isJdbcBasedOAuth2AuthorizationService || isJdbcBasedRegisteredClientRepository) - && !this.jackson2Contributed) { - Jackson2ConfigurationBeanRegistrationAotContribution jackson2Contribution = - new Jackson2ConfigurationBeanRegistrationAotContribution(); - this.jackson2Contributed = true; - return jackson2Contribution; - } - // @formatter:on - return null; - } - - private static class Jackson2ConfigurationBeanRegistrationAotContribution - implements BeanRegistrationAotContribution { - - private final BindingReflectionHintsRegistrar reflectionHintsRegistrar = new BindingReflectionHintsRegistrar(); - - @Override - public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { - registerHints(generationContext.getRuntimeHints()); - } - - private void registerHints(RuntimeHints hints) { - // Collections -> UnmodifiableSet, UnmodifiableList, UnmodifiableMap, - // UnmodifiableRandomAccessList, etc. - hints.reflection().registerType(Collections.class, MemberCategory.DECLARED_CLASSES); - - // HashSet - hints.reflection() - .registerType(HashSet.class, MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); - - // Spring Security and Spring Authorization Server - hints.reflection() - .registerTypes(Arrays.asList(TypeReference.of(AbstractAuthenticationToken.class), - TypeReference.of(DefaultSavedRequest.Builder.class), - TypeReference.of(WebAuthenticationDetails.class), - TypeReference.of(UsernamePasswordAuthenticationToken.class), TypeReference.of(User.class), - TypeReference.of(DefaultOidcUser.class), TypeReference.of(DefaultOAuth2User.class), - TypeReference.of(OidcUserAuthority.class), TypeReference.of(OAuth2UserAuthority.class), - TypeReference.of(SimpleGrantedAuthority.class), TypeReference.of(OidcIdToken.class), - TypeReference.of(AbstractOAuth2Token.class), TypeReference.of(OidcUserInfo.class), - TypeReference.of(OAuth2TokenExchangeActor.class), - TypeReference.of(OAuth2AuthorizationRequest.class), - TypeReference.of(OAuth2TokenExchangeCompositeAuthenticationToken.class), - TypeReference.of(AuthorizationGrantType.class), - TypeReference.of(OAuth2AuthorizationResponseType.class), - TypeReference.of(OAuth2TokenFormat.class)), - (builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS)); - - // Jackson Modules - Spring Security and Spring Authorization Server - hints.reflection() - .registerTypes( - Arrays.asList(TypeReference.of(CoreJackson2Module.class), - TypeReference.of(WebServletJackson2Module.class), - TypeReference.of(OAuth2AuthorizationServerJackson2Module.class)), - (builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS)); - - // Jackson Mixins - Spring Security and Spring Authorization Server - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.UnmodifiableSetMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.UnmodifiableListMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.UnmodifiableMapMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.server.authorization.jackson2.UnmodifiableMapMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.server.authorization.jackson2.HashSetMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.web.jackson2.DefaultSavedRequestMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.web.jackson2.WebAuthenticationDetailsMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.UserMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeActorMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenExchangeCompositeAuthenticationTokenMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin")); - - // Check if Spring Security OAuth2 Client is on classpath - if (ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration", - ClassUtils.getDefaultClassLoader())) { - - // Jackson Module (and required types) - Spring Security OAuth2 Client - hints.reflection() - .registerTypes(Arrays.asList( - TypeReference - .of("org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"), - TypeReference - .of("org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken")), - (builder) -> builder.withMembers(MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_METHODS)); - - // Jackson Mixins - Spring Security OAuth2 Client - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass( - "org.springframework.security.oauth2.client.jackson2.OAuth2AuthenticationTokenMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOidcUserMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.DefaultOAuth2UserMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserAuthorityMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.OAuth2UserAuthorityMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.OidcIdTokenMixin")); - this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), - loadClass("org.springframework.security.oauth2.client.jackson2.OidcUserInfoMixin")); - } - } - - private static Class loadClass(String className) { - try { - return Class.forName(className); - } - catch (ClassNotFoundException ex) { - throw new RuntimeException(ex); - } - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java deleted file mode 100644 index 63d025a57..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} base implementation for the OAuth 2.0 Authorization Request - * used in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 1.5 - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2PushedAuthorizationRequestAuthenticationToken - */ -abstract class AbstractOAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -5813797478091794517L; - - private final String authorizationUri; - - private final String clientId; - - private final Authentication principal; - - private final String redirectUri; - - private final String state; - - private final Set scopes; - - private final Map additionalParameters; - - protected AbstractOAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, @Nullable String redirectUri, @Nullable String state, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.hasText(authorizationUri, "authorizationUri cannot be empty"); - Assert.hasText(clientId, "clientId cannot be empty"); - Assert.notNull(principal, "principal cannot be null"); - this.authorizationUri = authorizationUri; - this.clientId = clientId; - this.principal = principal; - this.redirectUri = redirectUri; - this.state = state; - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the authorization URI. - * @return the authorization URI - */ - public String getAuthorizationUri() { - return this.authorizationUri; - } - - /** - * Returns the client identifier. - * @return the client identifier - */ - public String getClientId() { - return this.clientId; - } - - /** - * Returns the redirect uri. - * @return the redirect uri - */ - @Nullable - public String getRedirectUri() { - return this.redirectUri; - } - - /** - * Returns the state. - * @return the state - */ - @Nullable - public String getState() { - return this.state; - } - - /** - * Returns the requested (or authorized) scope(s). - * @return the requested (or authorized) scope(s), or an empty {@code Set} if not - * available - */ - public Set getScopes() { - return this.scopes; - } - - /** - * Returns the additional parameters. - * @return the additional parameters, or an empty {@code Map} if not available - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java deleted file mode 100644 index 3c60a7a0c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.authentication; - -import java.time.Instant; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client - * Authentication, which authenticates the {@link OAuth2ParameterNames#CLIENT_SECRET - * client_secret} parameter. - * - * @author Patryk Kostrzewa - * @author Joe Grandja - * @since 0.2.3 - * @see AuthenticationProvider - * @see OAuth2ClientAuthenticationToken - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see PasswordEncoder - */ -public final class ClientSecretAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final CodeVerifierAuthenticator codeVerifierAuthenticator; - - private PasswordEncoder passwordEncoder; - - /** - * Constructs a {@code ClientSecretAuthenticationProvider} using the provided - * parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public ClientSecretAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService); - this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - /** - * Sets the {@link PasswordEncoder} used to validate the - * {@link RegisteredClient#getClientSecret() client secret}. If not set, the client - * secret will be compared using - * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}. - * @param passwordEncoder the {@link PasswordEncoder} used to validate the client - * secret - */ - public void setPasswordEncoder(PasswordEncoder passwordEncoder) { - Assert.notNull(passwordEncoder, "passwordEncoder cannot be null"); - this.passwordEncoder = passwordEncoder; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; - - // @formatter:off - if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientAuthentication.getClientAuthenticationMethod()) && - !ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthentication.getClientAuthenticationMethod())) { - return null; - } - // @formatter:on - - String clientId = clientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getClientAuthenticationMethods() - .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); - } - - if (clientAuthentication.getCredentials() == null) { - throwInvalidClient("credentials"); - } - - String clientSecret = clientAuthentication.getCredentials().toString(); - if (!this.passwordEncoder.matches(clientSecret, registeredClient.getClientSecret())) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: client_secret does not match" + " for registered client '%s'", - registeredClient.getId())); - } - throwInvalidClient(OAuth2ParameterNames.CLIENT_SECRET); - } - - if (registeredClient.getClientSecretExpiresAt() != null - && Instant.now().isAfter(registeredClient.getClientSecretExpiresAt())) { - throwInvalidClient("client_secret_expires_at"); - } - - if (this.passwordEncoder.upgradeEncoding(registeredClient.getClientSecret())) { - registeredClient = RegisteredClient.from(registeredClient) - .clientSecret(this.passwordEncoder.encode(clientSecret)) - .build(); - this.registeredClientRepository.save(registeredClient); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client authentication parameters"); - } - - // Validate the "code_verifier" parameter for the confidential client, if - // available - this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated client secret"); - } - - return new OAuth2ClientAuthenticationToken(registeredClient, - clientAuthentication.getClientAuthenticationMethod(), clientAuthentication.getCredentials()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static void throwInvalidClient(String parameterName) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java deleted file mode 100644 index df3ff81a9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An authenticator used for OAuth 2.0 Client Authentication, which authenticates the - * {@link PkceParameterNames#CODE_VERIFIER code_verifier} parameter. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2ClientAuthenticationToken - * @see OAuth2AuthorizationService - */ -final class CodeVerifierAuthenticator { - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - CodeVerifierAuthenticator(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.authorizationService = authorizationService; - } - - void authenticateRequired(OAuth2ClientAuthenticationToken clientAuthentication, RegisteredClient registeredClient) { - if (!authenticate(clientAuthentication, registeredClient)) { - throwInvalidGrant(PkceParameterNames.CODE_VERIFIER); - } - } - - void authenticateIfAvailable(OAuth2ClientAuthenticationToken clientAuthentication, - RegisteredClient registeredClient) { - authenticate(clientAuthentication, registeredClient); - } - - private boolean authenticate(OAuth2ClientAuthenticationToken clientAuthentication, - RegisteredClient registeredClient) { - - Map parameters = clientAuthentication.getAdditionalParameters(); - if (!authorizationCodeGrant(parameters)) { - return false; - } - - OAuth2Authorization authorization = this.authorizationService - .findByToken((String) parameters.get(OAuth2ParameterNames.CODE), AUTHORIZATION_CODE_TOKEN_TYPE); - if (authorization == null) { - throwInvalidGrant(OAuth2ParameterNames.CODE); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with authorization code"); - } - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - - String codeChallenge = (String) authorizationRequest.getAdditionalParameters() - .get(PkceParameterNames.CODE_CHALLENGE); - String codeVerifier = (String) parameters.get(PkceParameterNames.CODE_VERIFIER); - if (!StringUtils.hasText(codeChallenge)) { - if (registeredClient.getClientSettings().isRequireProofKey() || StringUtils.hasText(codeVerifier)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: code_challenge is required" + " for registered client '%s'", - registeredClient.getId())); - } - throwInvalidGrant(PkceParameterNames.CODE_CHALLENGE); - } - else { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not authenticate code verifier since requireProofKey=false"); - } - return false; - } - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated code verifier parameters"); - } - - String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters() - .get(PkceParameterNames.CODE_CHALLENGE_METHOD); - if (!codeVerifierValid(codeVerifier, codeChallenge, codeChallengeMethod)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: code_verifier is missing or invalid" + " for registered client '%s'", - registeredClient.getId())); - } - throwInvalidGrant(PkceParameterNames.CODE_VERIFIER); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated code verifier"); - } - - return true; - } - - private static boolean authorizationCodeGrant(Map parameters) { - if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue() - .equals(parameters.get(OAuth2ParameterNames.GRANT_TYPE))) { - return false; - } - if (!StringUtils.hasText((String) parameters.get(OAuth2ParameterNames.CODE))) { - throwInvalidGrant(OAuth2ParameterNames.CODE); - } - return true; - } - - private boolean codeVerifierValid(String codeVerifier, String codeChallenge, String codeChallengeMethod) { - if (!StringUtils.hasText(codeVerifier)) { - return false; - } - else if ("S256".equals(codeChallengeMethod)) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII)); - String encodedVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - return encodedVerifier.equals(codeChallenge); - } - catch (NoSuchAlgorithmException ex) { - // It is unlikely that SHA-256 is not available on the server. If it is - // not available, - // there will likely be bigger issues as well. We default to SERVER_ERROR. - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR); - } - } - return false; - } - - private static void throwInvalidGrant(String parameterName) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT, - "Client authentication failed: " + parameterName, null); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java deleted file mode 100644 index a48592da2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.jwt.DPoPProofContext; -import org.springframework.security.oauth2.jwt.DPoPProofJwtDecoderFactory; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.util.StringUtils; - -/** - * A verifier for DPoP Proof {@link Jwt}'s. - * - * @author Joe Grandja - * @since 1.5 - * @see DPoPProofJwtDecoderFactory - * @see RFC 9449 - * OAuth 2.0 Demonstrating Proof of Possession (DPoP) - */ -final class DPoPProofVerifier { - - private static final JwtDecoderFactory dPoPProofVerifierFactory = new DPoPProofJwtDecoderFactory(); - - private DPoPProofVerifier() { - } - - static Jwt verifyIfAvailable(OAuth2AuthorizationGrantAuthenticationToken authorizationGrantAuthentication) { - String dPoPProof = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_proof"); - if (!StringUtils.hasText(dPoPProof)) { - return null; - } - - String method = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_method"); - String targetUri = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_target_uri"); - - Jwt dPoPProofJwt; - try { - // @formatter:off - DPoPProofContext dPoPProofContext = DPoPProofContext.withDPoPProof(dPoPProof) - .method(method) - .targetUri(targetUri) - .build(); - // @formatter:on - JwtDecoder dPoPProofVerifier = dPoPProofVerifierFactory.createDecoder(dPoPProofContext); - dPoPProofJwt = dPoPProofVerifier.decode(dPoPProof); - } - catch (Exception ex) { - throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF), ex); - } - - return dPoPProofJwt; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java deleted file mode 100644 index 6352d6d54..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.security.oauth2.jwt.JwtException; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client - * Authentication, which authenticates the {@link Jwt} - * {@link OAuth2ParameterNames#CLIENT_ASSERTION client_assertion} parameter. - * - * @author Rafal Lewczuk - * @author Joe Grandja - * @since 0.2.3 - * @see AuthenticationProvider - * @see OAuth2ClientAuthenticationToken - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see JwtClientAssertionDecoderFactory - */ -public final class JwtClientAssertionAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - - private static final ClientAuthenticationMethod JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD = new ClientAuthenticationMethod( - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final CodeVerifierAuthenticator codeVerifierAuthenticator; - - private JwtDecoderFactory jwtDecoderFactory; - - /** - * Constructs a {@code JwtClientAssertionAuthenticationProvider} using the provided - * parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public JwtClientAssertionAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService); - this.jwtDecoderFactory = new JwtClientAssertionDecoderFactory(); - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; - - if (!JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD.equals(clientAuthentication.getClientAuthenticationMethod())) { - return null; - } - - String clientId = clientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - // @formatter:off - if (!registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.PRIVATE_KEY_JWT) && - !registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) { - throwInvalidClient("authentication_method"); - } - // @formatter:on - - if (clientAuthentication.getCredentials() == null) { - throwInvalidClient("credentials"); - } - - Jwt jwtAssertion = null; - JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(registeredClient); - try { - jwtAssertion = jwtDecoder.decode(clientAuthentication.getCredentials().toString()); - } - catch (JwtException ex) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ASSERTION, ex); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client authentication parameters"); - } - - // Validate the "code_verifier" parameter for the confidential client, if - // available - this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient); - - // @formatter:off - ClientAuthenticationMethod clientAuthenticationMethod = - (registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm() instanceof SignatureAlgorithm) ? - ClientAuthenticationMethod.PRIVATE_KEY_JWT : - ClientAuthenticationMethod.CLIENT_SECRET_JWT; - // @formatter:on - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated client assertion"); - } - - return new OAuth2ClientAuthenticationToken(registeredClient, clientAuthenticationMethod, jwtAssertion); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the - * specified {@link RegisteredClient} and is used for authenticating a {@link Jwt} - * Bearer Token during OAuth 2.0 Client Authentication. The default factory is - * {@link JwtClientAssertionDecoderFactory}. - * @param jwtDecoderFactory the {@link JwtDecoderFactory} that provides a - * {@link JwtDecoder} for the specified {@link RegisteredClient} - * @since 0.4.0 - */ - public void setJwtDecoderFactory(JwtDecoderFactory jwtDecoderFactory) { - Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null"); - this.jwtDecoderFactory = jwtDecoderFactory; - } - - private static void throwInvalidClient(String parameterName) { - throwInvalidClient(parameterName, null); - } - - private static void throwInvalidClient(String parameterName, Throwable cause) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error, error.toString(), cause); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java deleted file mode 100644 index f7738ccb8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactory.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; - -import javax.crypto.spec.SecretKeySpec; - -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2TokenValidator; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.jwt.JwtClaimValidator; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.security.oauth2.jwt.JwtTimestampValidator; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} for the - * specified {@link RegisteredClient} and is used for authenticating a {@link Jwt} Bearer - * Token during OAuth 2.0 Client Authentication. - * - * @author Rafal Lewczuk - * @author Joe Grandja - * @since 0.4.0 - * @see JwtDecoderFactory - * @see RegisteredClient - * @see OAuth2TokenValidator - * @see JwtClientAssertionAuthenticationProvider - * @see ClientAuthenticationMethod#PRIVATE_KEY_JWT - * @see ClientAuthenticationMethod#CLIENT_SECRET_JWT - */ -public final class JwtClientAssertionDecoderFactory implements JwtDecoderFactory { - - /** - * The default {@code OAuth2TokenValidator} factory that validates the - * {@link JwtClaimNames#ISS iss}, {@link JwtClaimNames#SUB sub}, - * {@link JwtClaimNames#AUD aud}, {@link JwtClaimNames#EXP exp} and - * {@link JwtClaimNames#NBF nbf} claims of the {@link Jwt} for the specified - * {@link RegisteredClient}. - */ - public static final Function> DEFAULT_JWT_VALIDATOR_FACTORY = defaultJwtValidatorFactory(); - - private static final String JWT_CLIENT_AUTHENTICATION_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7523#section-3"; - - private static final Map JCA_ALGORITHM_MAPPINGS; - - static { - Map mappings = new HashMap<>(); - mappings.put(MacAlgorithm.HS256, "HmacSHA256"); - mappings.put(MacAlgorithm.HS384, "HmacSHA384"); - mappings.put(MacAlgorithm.HS512, "HmacSHA512"); - JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings); - } - - private static final RestTemplate restTemplate = new RestTemplate(); - - static { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - requestFactory.setConnectTimeout(15_000); - requestFactory.setReadTimeout(15_000); - restTemplate.setRequestFactory(requestFactory); - } - - private final Map jwtDecoders = new ConcurrentHashMap<>(); - - private Function> jwtValidatorFactory = DEFAULT_JWT_VALIDATOR_FACTORY; - - @Override - public JwtDecoder createDecoder(RegisteredClient registeredClient) { - Assert.notNull(registeredClient, "registeredClient cannot be null"); - return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) -> { - NimbusJwtDecoder jwtDecoder = buildDecoder(registeredClient); - jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(registeredClient)); - return jwtDecoder; - }); - } - - /** - * Sets the factory that provides an {@link OAuth2TokenValidator} for the specified - * {@link RegisteredClient} and is used by the {@link JwtDecoder}. The default - * {@code OAuth2TokenValidator} factory is - * {@link #DEFAULT_JWT_VALIDATOR_FACTORY}. - * @param jwtValidatorFactory the factory that provides an - * {@link OAuth2TokenValidator} for the specified {@link RegisteredClient} - */ - public void setJwtValidatorFactory(Function> jwtValidatorFactory) { - Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null"); - this.jwtValidatorFactory = jwtValidatorFactory; - } - - private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) { - JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings() - .getTokenEndpointAuthenticationSigningAlgorithm(); - if (jwsAlgorithm instanceof SignatureAlgorithm) { - String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl(); - if (!StringUtils.hasText(jwkSetUrl)) { - OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Failed to find a Signature Verifier for Client: '" + registeredClient.getId() - + "'. Check to ensure you have configured the JWK Set URL.", - JWT_CLIENT_AUTHENTICATION_ERROR_URI); - throw new OAuth2AuthenticationException(oauth2Error); - } - return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl) - .jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm) - .restOperations(restTemplate) - .build(); - } - if (jwsAlgorithm instanceof MacAlgorithm) { - String clientSecret = registeredClient.getClientSecret(); - if (!StringUtils.hasText(clientSecret)) { - OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Failed to find a Signature Verifier for Client: '" + registeredClient.getId() - + "'. Check to ensure you have configured the client secret.", - JWT_CLIENT_AUTHENTICATION_ERROR_URI); - throw new OAuth2AuthenticationException(oauth2Error); - } - SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), - JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm)); - return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build(); - } - OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Failed to find a Signature Verifier for Client: '" + registeredClient.getId() - + "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'.", - JWT_CLIENT_AUTHENTICATION_ERROR_URI); - throw new OAuth2AuthenticationException(oauth2Error); - } - - private static Function> defaultJwtValidatorFactory() { - return (registeredClient) -> { - String clientId = registeredClient.getClientId(); - return new DelegatingOAuth2TokenValidator<>(new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals), - new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals), - new JwtClaimValidator<>(JwtClaimNames.AUD, containsAudience()), - new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull), new JwtTimestampValidator()); - }; - } - - private static Predicate> containsAudience() { - return (audienceClaim) -> { - if (CollectionUtils.isEmpty(audienceClaim)) { - return false; - } - List audienceList = getAudience(); - for (String audience : audienceClaim) { - if (audienceList.contains(audience)) { - return true; - } - } - return false; - }; - } - - private static List getAudience() { - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - if (!StringUtils.hasText(authorizationServerContext.getIssuer())) { - return Collections.emptyList(); - } - - AuthorizationServerSettings authorizationServerSettings = authorizationServerContext - .getAuthorizationServerSettings(); - List audience = new ArrayList<>(); - audience.add(authorizationServerContext.getIssuer()); - audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenEndpoint())); - audience.add(asUrl(authorizationServerContext.getIssuer(), - authorizationServerSettings.getTokenIntrospectionEndpoint())); - audience.add(asUrl(authorizationServerContext.getIssuer(), - authorizationServerSettings.getTokenRevocationEndpoint())); - audience.add(asUrl(authorizationServerContext.getIssuer(), - authorizationServerSettings.getPushedAuthorizationRequestEndpoint())); - return audience; - } - - private static String asUrl(String issuer, String endpoint) { - return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java deleted file mode 100644 index 9b08615a9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AccessTokenResponseAuthenticationSuccessHandler; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OAuth2AccessTokenAuthenticationToken} and additional information and is used - * when customizing the {@link OAuth2AccessTokenResponse}. - * - * @author Dmitriy Dubson - * @since 1.3 - * @see OAuth2AuthenticationContext - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2AccessTokenResponse - * @see OAuth2AccessTokenResponseAuthenticationSuccessHandler#setAccessTokenResponseCustomizer(Consumer) - */ -public final class OAuth2AccessTokenAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OAuth2AccessTokenAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link OAuth2AccessTokenResponse.Builder access token response - * builder}. - * @return the {@link OAuth2AccessTokenResponse.Builder} - */ - public OAuth2AccessTokenResponse.Builder getAccessTokenResponse() { - return get(OAuth2AccessTokenResponse.Builder.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OAuth2AccessTokenAuthenticationToken}. - * @param authentication the {@link OAuth2AccessTokenAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OAuth2AccessTokenAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OAuth2AccessTokenAuthenticationContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OAuth2AccessTokenAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link OAuth2AccessTokenResponse.Builder access token response - * builder}. - * @param accessTokenResponse the {@link OAuth2AccessTokenResponse.Builder} - * @return the {@link Builder} for further configuration - */ - public Builder accessTokenResponse(OAuth2AccessTokenResponse.Builder accessTokenResponse) { - return put(OAuth2AccessTokenResponse.Builder.class, accessTokenResponse); - } - - /** - * Builds a new {@link OAuth2AccessTokenAuthenticationContext}. - * @return the {@link OAuth2AccessTokenAuthenticationContext} - */ - @Override - public OAuth2AccessTokenAuthenticationContext build() { - Assert.notNull(get(OAuth2AccessTokenResponse.Builder.class), "accessTokenResponse cannot be null"); - return new OAuth2AccessTokenAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java deleted file mode 100644 index 5bb412e2e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used when issuing an OAuth 2.0 Access Token - * and (optional) Refresh Token. - * - * @author Joe Grandja - * @author Madhu Bhat - * @since 0.0.1 - * @see AbstractAuthenticationToken - * @see RegisteredClient - * @see OAuth2AccessToken - * @see OAuth2RefreshToken - * @see OAuth2ClientAuthenticationToken - */ -public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = 2773767853287774441L; - - private final RegisteredClient registeredClient; - - private final Authentication clientPrincipal; - - private final OAuth2AccessToken accessToken; - - private final OAuth2RefreshToken refreshToken; - - private final Map additionalParameters; - - /** - * Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided - * parameters. - * @param registeredClient the registered client - * @param clientPrincipal the authenticated client principal - * @param accessToken the access token - */ - public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal, - OAuth2AccessToken accessToken) { - this(registeredClient, clientPrincipal, accessToken, null); - } - - /** - * Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided - * parameters. - * @param registeredClient the registered client - * @param clientPrincipal the authenticated client principal - * @param accessToken the access token - * @param refreshToken the refresh token - */ - public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal, - OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) { - this(registeredClient, clientPrincipal, accessToken, refreshToken, Collections.emptyMap()); - } - - /** - * Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided - * parameters. - * @param registeredClient the registered client - * @param clientPrincipal the authenticated client principal - * @param accessToken the access token - * @param refreshToken the refresh token - * @param additionalParameters the additional parameters - */ - public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal, - OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken, - Map additionalParameters) { - super(Collections.emptyList()); - Assert.notNull(registeredClient, "registeredClient cannot be null"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - Assert.notNull(accessToken, "accessToken cannot be null"); - Assert.notNull(additionalParameters, "additionalParameters cannot be null"); - this.registeredClient = registeredClient; - this.clientPrincipal = clientPrincipal; - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.additionalParameters = additionalParameters; - } - - @Override - public Object getPrincipal() { - return this.clientPrincipal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return this.registeredClient; - } - - /** - * Returns the {@link OAuth2AccessToken access token}. - * @return the {@link OAuth2AccessToken} - */ - public OAuth2AccessToken getAccessToken() { - return this.accessToken; - } - - /** - * Returns the {@link OAuth2RefreshToken refresh token}. - * @return the {@link OAuth2RefreshToken} or {@code null} if not available - */ - @Nullable - public OAuth2RefreshToken getRefreshToken() { - return this.refreshToken; - } - - /** - * Returns the additional parameters. - * @return a {@code Map} of the additional parameters, may be empty - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java deleted file mode 100644 index 12bc91d91..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.server.authorization.context.Context; -import org.springframework.util.Assert; - -/** - * A context that holds an {@link Authentication} and (optionally) additional information - * and is used in an {@link AuthenticationProvider}. - * - * @author Joe Grandja - * @since 0.2.0 - * @see Context - */ -public interface OAuth2AuthenticationContext extends Context { - - /** - * Returns the {@link Authentication} associated to the context. - * @param the type of the {@code Authentication} - * @return the {@link Authentication} - */ - @SuppressWarnings("unchecked") - default T getAuthentication() { - return (T) get(Authentication.class); - } - - /** - * A builder for subclasses of {@link OAuth2AuthenticationContext}. - * - * @param the type of the authentication context - * @param the type of the builder - * @since 0.2.1 - */ - abstract class AbstractBuilder> { - - private final Map context = new HashMap<>(); - - protected AbstractBuilder(Authentication authentication) { - Assert.notNull(authentication, "authentication cannot be null"); - put(Authentication.class, authentication); - } - - /** - * Associates an attribute. - * @param key the key for the attribute - * @param value the value of the attribute - * @return the {@link AbstractBuilder} for further configuration - */ - public B put(Object key, Object value) { - Assert.notNull(key, "key cannot be null"); - Assert.notNull(value, "value cannot be null"); - getContext().put(key, value); - return getThis(); - } - - /** - * A {@code Consumer} of the attributes {@code Map} allowing the ability to add, - * replace, or remove. - * @param contextConsumer a {@link Consumer} of the attributes {@code Map} - * @return the {@link AbstractBuilder} for further configuration - */ - public B context(Consumer> contextConsumer) { - contextConsumer.accept(getContext()); - return getThis(); - } - - @SuppressWarnings("unchecked") - protected V get(Object key) { - return (V) getContext().get(key); - } - - protected Map getContext() { - return this.context; - } - - @SuppressWarnings("unchecked") - protected final B getThis() { - return (B) this; - } - - /** - * Builds a new {@link OAuth2AuthenticationContext}. - * @return the {@link OAuth2AuthenticationContext} - */ - public abstract T build(); - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java deleted file mode 100644 index 2448f4b8d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Map; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.util.CollectionUtils; - -/** - * Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s. - * - * @author Joe Grandja - * @since 0.0.3 - */ -final class OAuth2AuthenticationProviderUtils { - - private OAuth2AuthenticationProviderUtils() { - } - - static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) { - OAuth2ClientAuthenticationToken clientPrincipal = null; - if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) { - clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal(); - } - if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { - return clientPrincipal; - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); - } - - static OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token, - OAuth2TokenContext accessTokenContext) { - - OAuth2AccessToken.TokenType tokenType = OAuth2AccessToken.TokenType.BEARER; - if (token instanceof ClaimAccessor claimAccessor) { - Map cnfClaims = claimAccessor.getClaimAsMap("cnf"); - if (!CollectionUtils.isEmpty(cnfClaims) && cnfClaims.containsKey("jkt")) { - tokenType = OAuth2AccessToken.TokenType.DPOP; - } - } - OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, token.getTokenValue(), token.getIssuedAt(), - token.getExpiresAt(), accessTokenContext.getAuthorizedScopes()); - OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient() - .getTokenSettings() - .getAccessTokenFormat(); - builder.token(accessToken, (metadata) -> { - if (token instanceof ClaimAccessor claimAccessor) { - metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims()); - } - metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false); - metadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue()); - }); - - return accessToken; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java deleted file mode 100644 index 51e3bd81e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -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.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Code - * Grant. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @since 0.0.1 - * @see OAuth2AuthorizationCodeAuthenticationToken - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see Section 4.1 Authorization - * Code Grant - * @see Section 4.1.3 Access - * Token Request - */ -public final class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - private SessionRegistry sessionRegistry; - - /** - * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the - * provided parameters. - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - * @since 0.2.3 - */ - public OAuth2AuthorizationCodeAuthenticationProvider(OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2Authorization authorization = this.authorizationService - .findByToken(authorizationCodeAuthentication.getCode(), AUTHORIZATION_CODE_TOKEN_TYPE); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with authorization code"); - } - - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - - if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) { - if (!authorizationCode.isInvalidated()) { - // Invalidate the authorization code given that a different client is - // attempting to use it - authorization = OAuth2Authorization.from(authorization) - .invalidate(authorizationCode.getToken()) - .build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format("Invalidated authorization code used by registered client '%s'", - registeredClient.getId())); - } - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (StringUtils.hasText(authorizationRequest.getRedirectUri()) - && !authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: redirect_uri does not match" + " for registered client '%s'", - registeredClient.getId())); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (!authorizationCode.isActive()) { - if (authorizationCode.isInvalidated()) { - OAuth2Authorization.Token token = (authorization.getRefreshToken() != null) - ? authorization.getRefreshToken() : authorization.getAccessToken(); - if (token != null) { - // Invalidate the access (and refresh) token as the client is - // attempting to use the authorization code more than once - authorization = OAuth2Authorization.from(authorization).invalidate(token.getToken()).build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format( - "Invalidated authorization token(s) previously issued to registered client '%s'", - registeredClient.getId())); - } - } - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - // Verify the DPoP Proof (if available) - Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(authorizationCodeAuthentication); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated token request parameters"); - } - - Authentication principal = authorization.getAttribute(Principal.class.getName()); - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authorizationCodeAuthentication); - // @formatter:on - if (dPoPProof != null) { - tokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProof); - } - - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization); - - // ----- Access token ----- - OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); - OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); - if (generatedAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the access token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated access token"); - } - - OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder, - generatedAccessToken, tokenContext); - - // ----- Refresh token ----- - OAuth2RefreshToken refreshToken = null; - // Do not issue refresh token to public client - if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) { - tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); - OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); - if (generatedRefreshToken != null) { - if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate a valid refresh token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated refresh token"); - } - - refreshToken = (OAuth2RefreshToken) generatedRefreshToken; - authorizationBuilder.refreshToken(refreshToken); - } - } - - // ----- ID token ----- - OidcIdToken idToken; - if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) { - SessionInformation sessionInformation = getSessionInformation(principal); - if (sessionInformation != null) { - try { - // Compute (and use) hash for Session ID - sessionInformation = new SessionInformation(sessionInformation.getPrincipal(), - createHash(sessionInformation.getSessionId()), sessionInformation.getLastRequest()); - } - catch (NoSuchAlgorithmException ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Failed to compute hash for Session ID.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - tokenContextBuilder.put(SessionInformation.class, sessionInformation); - } - // @formatter:off - tokenContext = tokenContextBuilder - .tokenType(ID_TOKEN_TOKEN_TYPE) - .authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token - .build(); - // @formatter:on - OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext); - if (!(generatedIdToken instanceof Jwt)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the ID token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated id token"); - } - - idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(), - generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims()); - authorizationBuilder.token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())); - } - else { - idToken = null; - } - - // Invalidate the authorization code as it can only be used once - authorizationBuilder.invalidate(authorizationCode.getToken()); - - authorization = authorizationBuilder.build(); - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - Map additionalParameters = Collections.emptyMap(); - if (idToken != null) { - additionalParameters = new HashMap<>(); - additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue()); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated token request"); - } - - return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, - additionalParameters); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link SessionRegistry} used to track OpenID Connect sessions. - * @param sessionRegistry the {@link SessionRegistry} used to track OpenID Connect - * sessions - * @since 1.1 - */ - public void setSessionRegistry(SessionRegistry sessionRegistry) { - Assert.notNull(sessionRegistry, "sessionRegistry cannot be null"); - this.sessionRegistry = sessionRegistry; - } - - private SessionInformation getSessionInformation(Authentication principal) { - SessionInformation sessionInformation = null; - if (this.sessionRegistry != null) { - List sessions = this.sessionRegistry.getAllSessions(principal.getPrincipal(), false); - if (!CollectionUtils.isEmpty(sessions)) { - sessionInformation = sessions.get(0); - if (sessions.size() > 1) { - // Get the most recent session - sessions = new ArrayList<>(sessions); - sessions.sort(Comparator.comparing(SessionInformation::getLastRequest)); - sessionInformation = sessions.get(sessions.size() - 1); - } - } - } - return sessionInformation; - } - - private static String createHash(String value) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java deleted file mode 100644 index 63e7c09db..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.authentication; - -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for the OAuth 2.0 Authorization Code - * Grant. - * - * @author Joe Grandja - * @author Madhu Bhat - * @author Daniel Garnier-Moiroux - * @since 0.0.1 - * @see OAuth2AuthorizationGrantAuthenticationToken - * @see OAuth2AuthorizationCodeAuthenticationProvider - */ -public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { - - private final String code; - - private final String redirectUri; - - /** - * Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided - * parameters. - * @param code the authorization code - * @param clientPrincipal the authenticated client principal - * @param redirectUri the redirect uri - * @param additionalParameters the additional parameters - */ - public OAuth2AuthorizationCodeAuthenticationToken(String code, Authentication clientPrincipal, - @Nullable String redirectUri, @Nullable Map additionalParameters) { - super(AuthorizationGrantType.AUTHORIZATION_CODE, clientPrincipal, additionalParameters); - Assert.hasText(code, "code cannot be empty"); - this.code = code; - this.redirectUri = redirectUri; - } - - /** - * Returns the authorization code. - * @return the authorization code - */ - public String getCode() { - return this.code; - } - - /** - * Returns the redirect uri. - * @return the redirect uri - */ - @Nullable - public String getRedirectUri() { - return this.redirectUri; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java deleted file mode 100644 index aa051704a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.time.Instant; -import java.util.Base64; - -import org.springframework.lang.Nullable; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -/** - * An {@link OAuth2TokenGenerator} that generates an {@link OAuth2AuthorizationCode}. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2TokenGenerator - * @see OAuth2AuthorizationCode - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - * @see OAuth2AuthorizationConsentAuthenticationProvider - */ -final class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator authorizationCodeGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - @Nullable - @Override - public OAuth2AuthorizationCode generate(OAuth2TokenContext context) { - if (context.getTokenType() == null || !OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) { - return null; - } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive()); - return new OAuth2AuthorizationCode(this.authorizationCodeGenerator.generateKey(), issuedAt, expiresAt); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationContext.java deleted file mode 100644 index 91b39a3ff..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationContext.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and additional information - * and is used when validating the OAuth 2.0 Authorization Request parameters, as well as, - * determining if authorization consent is required. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2AuthenticationContext - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthorizationConsentRequired(Predicate) - */ -public final class OAuth2AuthorizationCodeRequestAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OAuth2AuthorizationCodeRequestAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Returns the {@link OAuth2AuthorizationRequest authorization request}. - * @return the {@link OAuth2AuthorizationRequest} - * @since 1.3 - */ - @Nullable - public OAuth2AuthorizationRequest getAuthorizationRequest() { - return get(OAuth2AuthorizationRequest.class); - } - - /** - * Returns the {@link OAuth2AuthorizationConsent authorization consent}. - * @return the {@link OAuth2AuthorizationConsent} - * @since 1.3 - */ - @Nullable - public OAuth2AuthorizationConsent getAuthorizationConsent() { - return get(OAuth2AuthorizationConsent.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. - * @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OAuth2AuthorizationCodeRequestAuthenticationContext}. - */ - public static final class Builder - extends AbstractBuilder { - - private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} for further configuration - */ - public Builder registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Sets the {@link OAuth2AuthorizationRequest authorization request}. - * @param authorizationRequest the {@link OAuth2AuthorizationRequest} - * @return the {@link Builder} for further configuration - * @since 1.3 - */ - public Builder authorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { - return put(OAuth2AuthorizationRequest.class, authorizationRequest); - } - - /** - * Sets the {@link OAuth2AuthorizationConsent authorization consent}. - * @param authorizationConsent the {@link OAuth2AuthorizationConsent} - * @return the {@link Builder} for further configuration - * @since 1.3 - */ - public Builder authorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { - return put(OAuth2AuthorizationConsent.class, authorizationConsent); - } - - /** - * Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationContext}. - * @return the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} - */ - @Override - public OAuth2AuthorizationCodeRequestAuthenticationContext build() { - Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); - return new OAuth2AuthorizationCodeRequestAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java deleted file mode 100644 index 36409cdb2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.authentication; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; - -/** - * This exception is thrown by - * {@link OAuth2AuthorizationCodeRequestAuthenticationProvider} when an attempt to - * authenticate the OAuth 2.0 Authorization Request (or Consent) fails. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - */ -public class OAuth2AuthorizationCodeRequestAuthenticationException extends OAuth2AuthenticationException { - - private final OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication; - - /** - * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationException} using - * the provided parameters. - * @param error the {@link OAuth2Error OAuth 2.0 Error} - * @param authorizationCodeRequestAuthentication the {@link Authentication} instance - * of the OAuth 2.0 Authorization Request (or Consent) - */ - public OAuth2AuthorizationCodeRequestAuthenticationException(OAuth2Error error, - @Nullable OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) { - super(error); - this.authorizationCodeRequestAuthentication = authorizationCodeRequestAuthentication; - } - - /** - * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationException} using - * the provided parameters. - * @param error the {@link OAuth2Error OAuth 2.0 Error} - * @param cause the root cause - * @param authorizationCodeRequestAuthentication the {@link Authentication} instance - * of the OAuth 2.0 Authorization Request (or Consent) - */ - public OAuth2AuthorizationCodeRequestAuthenticationException(OAuth2Error error, Throwable cause, - @Nullable OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) { - super(error, cause); - this.authorizationCodeRequestAuthentication = authorizationCodeRequestAuthentication; - } - - /** - * Returns the {@link Authentication} instance of the OAuth 2.0 Authorization Request - * (or Consent), or {@code null} if not available. - * @return the {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - */ - @Nullable - public OAuth2AuthorizationCodeRequestAuthenticationToken getAuthorizationCodeRequestAuthentication() { - return this.authorizationCodeRequestAuthentication; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java deleted file mode 100644 index 81042cbf5..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization - * Request used in the Authorization Code Grant. - * - * @author Joe Grandja - * @author Steve Riesenberg - * @since 0.1.2 - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationValidator - * @see OAuth2AuthorizationCodeAuthenticationProvider - * @see OAuth2AuthorizationConsentAuthenticationProvider - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2AuthorizationConsentService - * @see Section 4.1.1 - * Authorization Request - * @see Section 3.1.2.1 - * Authentication Request - */ -public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( - Base64.getUrlEncoder()); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2TokenGenerator authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator(); - - private Consumer authenticationValidator = new OAuth2AuthorizationCodeRequestAuthenticationValidator(); - - private Predicate authorizationConsentRequired = OAuth2AuthorizationCodeRequestAuthenticationProvider::isAuthorizationConsentRequired; - - /** - * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using - * the provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param authorizationConsentService the authorization consent service - */ - public OAuth2AuthorizationCodeRequestAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, - OAuth2AuthorizationConsentService authorizationConsentService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.authorizationConsentService = authorizationConsentService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication; - - OAuth2Authorization pushedAuthorization = null; - String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters() - .get(OAuth2ParameterNames.REQUEST_URI); - if (StringUtils.hasText(requestUri)) { - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = null; - try { - pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri.parse(requestUri); - } - catch (Exception ex) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, - authorizationCodeRequestAuthentication, null); - } - - pushedAuthorization = this.authorizationService.findByToken(pushedAuthorizationRequestUri.getState(), - STATE_TOKEN_TYPE); - if (pushedAuthorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, - authorizationCodeRequestAuthentication, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with pushed authorization request"); - } - - OAuth2AuthorizationRequest authorizationRequest = pushedAuthorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - - if (!authorizationCodeRequestAuthentication.getClientId().equals(authorizationRequest.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, - authorizationCodeRequestAuthentication, null); - } - - if (Instant.now().isAfter(pushedAuthorizationRequestUri.getExpiresAt())) { - // Remove (effectively invalidating) the pushed authorization request - this.authorizationService.remove(pushedAuthorization); - if (this.logger.isWarnEnabled()) { - this.logger - .warn(LogMessage.format("Removed expired pushed authorization request for client id '%s'", - authorizationRequest.getClientId())); - } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, - authorizationCodeRequestAuthentication, null); - } - - authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationRequest.getClientId(), - (Authentication) authorizationCodeRequestAuthentication.getPrincipal(), - authorizationRequest.getRedirectUri(), authorizationRequest.getState(), - authorizationRequest.getScopes(), authorizationRequest.getAdditionalParameters()); - } - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(authorizationCodeRequestAuthentication.getClientId()); - if (registeredClient == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, - authorizationCodeRequestAuthentication, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext - .with(authorizationCodeRequestAuthentication) - .registeredClient(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder - .build(); - - // grant_type - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR - .accept(authenticationContext); - - // redirect_uri and scope - this.authenticationValidator.accept(authenticationContext); - - // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR - .accept(authenticationContext); - - // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) - Set promptValues = Collections.emptySet(); - if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) { - String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt"); - if (StringUtils.hasText(prompt)) { - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR - .accept(authenticationContext); - promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " "))); - } - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated authorization code request parameters"); - } - - // --------------- - // The request is valid - ensure the resource owner is authenticated - // --------------- - - Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal(); - if (!isPrincipalAuthenticated(principal)) { - if (promptValues.contains(OidcPrompt.NONE)) { - // Return an error instead of displaying the login page (via the - // configured AuthenticationEntryPoint) - throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient); - } - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not authenticate authorization code request since principal not authenticated"); - } - // Return the authorization request as-is where isAuthenticated() is false - return authorizationCodeRequestAuthentication; - } - - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri()) - .clientId(registeredClient.getClientId()) - .redirectUri(authorizationCodeRequestAuthentication.getRedirectUri()) - .scopes(authorizationCodeRequestAuthentication.getScopes()) - .state(authorizationCodeRequestAuthentication.getState()) - .additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters()) - .build(); - authenticationContextBuilder.authorizationRequest(authorizationRequest); - - OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService - .findById(registeredClient.getId(), principal.getName()); - if (currentAuthorizationConsent != null) { - authenticationContextBuilder.authorizationConsent(currentAuthorizationConsent); - } - - if (this.authorizationConsentRequired.test(authenticationContextBuilder.build())) { - if (promptValues.contains(OidcPrompt.NONE)) { - // Return an error instead of displaying the consent page - throwError("consent_required", "prompt", authorizationCodeRequestAuthentication, registeredClient); - } - - String state = DEFAULT_STATE_GENERATOR.generateKey(); - OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest) - .attribute(OAuth2ParameterNames.STATE, state) - .build(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated authorization consent state"); - } - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - if (pushedAuthorization != null) { - // Enforce one-time use by removing the pushed authorization request - this.authorizationService.remove(pushedAuthorization); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Removed authorization with pushed authorization request"); - } - } - - Set currentAuthorizedScopes = (currentAuthorizationConsent != null) - ? currentAuthorizationConsent.getScopes() : null; - - return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(), - registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null); - } - - OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationCodeRequestAuthentication, - registeredClient, null, authorizationRequest.getScopes()); - OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext); - if (authorizationCode == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the authorization code.", ERROR_URI); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated authorization code"); - } - - OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest) - .authorizedScopes(authorizationRequest.getScopes()) - .token(authorizationCode) - .build(); - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - if (pushedAuthorization != null) { - // Enforce one-time use by removing the pushed authorization request - this.authorizationService.remove(pushedAuthorization); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Removed authorization with pushed authorization request"); - } - } - - String redirectUri = authorizationRequest.getRedirectUri(); - if (!StringUtils.hasText(redirectUri)) { - redirectUri = registeredClient.getRedirectUris().iterator().next(); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated authorization code request"); - } - - return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(), - registeredClient.getClientId(), principal, authorizationCode, redirectUri, - authorizationRequest.getState(), authorizationRequest.getScopes()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link OAuth2TokenGenerator} that generates the - * {@link OAuth2AuthorizationCode}. - * @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates - * the {@link OAuth2AuthorizationCode} - * @since 0.2.3 - */ - public void setAuthorizationCodeGenerator( - OAuth2TokenGenerator authorizationCodeGenerator) { - Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null"); - this.authorizationCodeGenerator = authorizationCodeGenerator; - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Authorization Request parameters associated in the - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. The default - * authentication validator is - * {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}. - * - *

- * NOTE: The authentication validator MUST throw - * {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails. - * @param authenticationValidator the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Authorization Request parameters - * @since 0.4.0 - */ - public void setAuthenticationValidator( - Consumer authenticationValidator) { - Assert.notNull(authenticationValidator, "authenticationValidator cannot be null"); - this.authenticationValidator = authenticationValidator; - } - - /** - * Sets the {@code Predicate} used to determine if authorization consent is required. - * - *

- * The {@link OAuth2AuthorizationCodeRequestAuthenticationContext} gives the predicate - * access to the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}, as well - * as, the following context attributes: - *

    - *
  • The {@link RegisteredClient} associated with the authorization request.
  • - *
  • The {@link OAuth2AuthorizationRequest} containing the authorization request - * parameters.
  • - *
  • The {@link OAuth2AuthorizationConsent} previously granted to the - * {@link RegisteredClient}, or {@code null} if not available.
  • - *
- * @param authorizationConsentRequired the {@code Predicate} used to determine if - * authorization consent is required - * @since 1.3 - */ - public void setAuthorizationConsentRequired( - Predicate authorizationConsentRequired) { - Assert.notNull(authorizationConsentRequired, "authorizationConsentRequired cannot be null"); - this.authorizationConsentRequired = authorizationConsentRequired; - } - - private static boolean isAuthorizationConsentRequired( - OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) { - return false; - } - // 'openid' scope does not require consent - if (authenticationContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID) - && authenticationContext.getAuthorizationRequest().getScopes().size() == 1) { - return false; - } - - if (authenticationContext.getAuthorizationConsent() != null && authenticationContext.getAuthorizationConsent() - .getScopes() - .containsAll(authenticationContext.getAuthorizationRequest().getScopes())) { - return false; - } - - return true; - } - - private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, - Authentication principal, OAuth2AuthorizationRequest authorizationRequest) { - return OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(principal.getName()) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .attribute(Principal.class.getName(), principal) - .attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest); - } - - private static OAuth2TokenContext createAuthorizationCodeTokenContext( - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2Authorization authorization, Set authorizedScopes) { - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal((Authentication) authorizationCodeRequestAuthentication.getPrincipal()) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE)) - .authorizedScopes(authorizedScopes) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authorizationCodeRequestAuthentication); - // @formatter:on - - if (authorization != null) { - tokenContextBuilder.authorization(authorization); - } - - return tokenContextBuilder.build(); - } - - private static boolean isPrincipalAuthenticated(Authentication principal) { - return principal != null && !AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) - && principal.isAuthenticated(); - } - - private static void throwError(String errorCode, String parameterName, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient) { - throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient, null); - } - - private static void throwError(String errorCode, String parameterName, String errorUri, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient, - authorizationRequest); - } - - private static void throwError(OAuth2Error error, String parameterName, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { - - String redirectUri = resolveRedirectUri(authorizationCodeRequestAuthentication, authorizationRequest, - registeredClient); - if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) - && (parameterName.equals(OAuth2ParameterNames.CLIENT_ID) - || parameterName.equals(OAuth2ParameterNames.STATE))) { - redirectUri = null; // Prevent redirects - } - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - authorizationCodeRequestAuthentication.getAuthorizationUri(), - authorizationCodeRequestAuthentication.getClientId(), - (Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri, - authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(), - authorizationCodeRequestAuthentication.getAdditionalParameters()); - - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, - authorizationCodeRequestAuthenticationResult); - } - - private static String resolveRedirectUri( - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) { - - if (authorizationCodeRequestAuthentication != null - && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { - return authorizationCodeRequestAuthentication.getRedirectUri(); - } - if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) { - return authorizationRequest.getRedirectUri(); - } - if (registeredClient != null) { - return registeredClient.getRedirectUris().iterator().next(); - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java deleted file mode 100644 index 093cc3636..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the OAuth 2.0 Authorization Request used - * in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - * @see OAuth2AuthorizationConsentAuthenticationProvider - */ -public class OAuth2AuthorizationCodeRequestAuthenticationToken - extends AbstractOAuth2AuthorizationCodeRequestAuthenticationToken { - - @Serial - private static final long serialVersionUID = -1946164725241393094L; - - private final OAuth2AuthorizationCode authorizationCode; - - /** - * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the {@code Principal} (Resource Owner) - * @param redirectUri the redirect uri - * @param state the state - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - * @since 0.4.0 - */ - public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, @Nullable String redirectUri, @Nullable String state, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(authorizationUri, clientId, principal, redirectUri, state, scopes, additionalParameters); - this.authorizationCode = null; - } - - /** - * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the {@code Principal} (Resource Owner) - * @param authorizationCode the {@link OAuth2AuthorizationCode} - * @param redirectUri the redirect uri - * @param state the state - * @param scopes the authorized scope(s) - * @since 0.4.0 - */ - public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, OAuth2AuthorizationCode authorizationCode, @Nullable String redirectUri, - @Nullable String state, @Nullable Set scopes) { - super(authorizationUri, clientId, principal, redirectUri, state, scopes, null); - Assert.notNull(authorizationCode, "authorizationCode cannot be null"); - this.authorizationCode = authorizationCode; - setAuthenticated(true); - } - - /** - * Returns the {@link OAuth2AuthorizationCode}. - * @return the {@link OAuth2AuthorizationCode} - */ - @Nullable - public OAuth2AuthorizationCode getAuthorizationCode() { - return this.authorizationCode; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java deleted file mode 100644 index 89d6c130c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Consumer} providing access to the - * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} containing an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and is the default - * {@link OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) - * authentication validator} used for validating specific OAuth 2.0 Authorization Request - * parameters used in the Authorization Code Grant. - * - *

- * The default implementation first validates - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()} and then - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}. If validation - * fails, an {@link OAuth2AuthorizationCodeRequestAuthenticationException} is thrown. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2AuthorizationCodeRequestAuthenticationContext - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) - * @see OAuth2PushedAuthorizationRequestAuthenticationProvider#setAuthenticationValidator(Consumer) - */ -public final class OAuth2AuthorizationCodeRequestAuthenticationValidator - implements Consumer { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; - - private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1"; - - private static final Log LOGGER = LogFactory.getLog(OAuth2AuthorizationCodeRequestAuthenticationValidator.class); - - static final Consumer DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR = OAuth2AuthorizationCodeRequestAuthenticationValidator::validateAuthorizationGrantType; - - static final Consumer DEFAULT_CODE_CHALLENGE_VALIDATOR = OAuth2AuthorizationCodeRequestAuthenticationValidator::validateCodeChallenge; - - static final Consumer DEFAULT_PROMPT_VALIDATOR = OAuth2AuthorizationCodeRequestAuthenticationValidator::validatePrompt; - - /** - * The default validator for - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()}. - */ - public static final Consumer DEFAULT_REDIRECT_URI_VALIDATOR = OAuth2AuthorizationCodeRequestAuthenticationValidator::validateRedirectUri; - - /** - * The default validator for - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}. - */ - public static final Consumer DEFAULT_SCOPE_VALIDATOR = OAuth2AuthorizationCodeRequestAuthenticationValidator::validateScope; - - private final Consumer authenticationValidator = DEFAULT_REDIRECT_URI_VALIDATOR - .andThen(DEFAULT_SCOPE_VALIDATOR); - - @Override - public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - this.authenticationValidator.accept(authenticationContext); - } - - private static void validateAuthorizationGrantType( - OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(LogMessage.format( - "Invalid request: requested grant_type is not allowed for registered client '%s'", - registeredClient.getId())); - } - throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID, - authorizationCodeRequestAuthentication, registeredClient); - } - } - - private static void validateRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - - String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri(); - - if (StringUtils.hasText(requestedRedirectUri)) { - // ***** redirect_uri is available in authorization request - - UriComponents requestedRedirect = null; - try { - requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build(); - } - catch (Exception ex) { - } - if (requestedRedirect == null || requestedRedirect.getFragment() != null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(LogMessage.format("Invalid request: redirect_uri is missing or contains a fragment" - + " for registered client '%s'", registeredClient.getId())); - } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - - if (!isLoopbackAddress(requestedRedirect.getHost())) { - // As per - // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-22#section-4.1.3 - // When comparing client redirect URIs against pre-registered URIs, - // authorization servers MUST utilize exact string matching. - if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - } - else { - // As per - // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-08#section-8.4.2 - // The authorization server MUST allow any port to be specified at the - // time of the request for loopback IP redirect URIs, to accommodate - // clients that obtain an available ephemeral port from the operating - // system at the time of the request. - boolean validRedirectUri = false; - for (String registeredRedirectUri : registeredClient.getRedirectUris()) { - UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri); - registeredRedirect.port(requestedRedirect.getPort()); - if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) { - validRedirectUri = true; - break; - } - } - if (!validRedirectUri) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(LogMessage.format( - "Invalid request: redirect_uri does not match for registered client '%s'", - registeredClient.getId())); - } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - } - - } - else { - // ***** redirect_uri is NOT available in authorization request - - if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) - || registeredClient.getRedirectUris().size() != 1) { - // redirect_uri is REQUIRED for OpenID Connect - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - } - } - - private static void validateScope(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - - Set requestedScopes = authorizationCodeRequestAuthentication.getScopes(); - Set allowedScopes = registeredClient.getScopes(); - if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - LogMessage.format("Invalid request: requested scope is not allowed for registered client '%s'", - registeredClient.getId())); - } - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, - authorizationCodeRequestAuthentication, registeredClient); - } - } - - private static void validateCodeChallenge( - OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - - // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) - String codeChallenge = (String) authorizationCodeRequestAuthentication.getAdditionalParameters() - .get(PkceParameterNames.CODE_CHALLENGE); - if (StringUtils.hasText(codeChallenge)) { - String codeChallengeMethod = (String) authorizationCodeRequestAuthentication.getAdditionalParameters() - .get(PkceParameterNames.CODE_CHALLENGE_METHOD); - if (!StringUtils.hasText(codeChallengeMethod) || !"S256".equals(codeChallengeMethod)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - } - else if (registeredClient.getClientSettings().isRequireProofKey()) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI, - authorizationCodeRequestAuthentication, registeredClient); - } - } - - private static void validatePrompt(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - - // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) - if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) { - String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt"); - if (StringUtils.hasText(prompt)) { - Set promptValues = new HashSet<>( - Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " "))); - if (promptValues.contains(OidcPrompt.NONE)) { - if (promptValues.contains(OidcPrompt.LOGIN) || promptValues.contains(OidcPrompt.CONSENT) - || promptValues.contains(OidcPrompt.SELECT_ACCOUNT)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt", authorizationCodeRequestAuthentication, - registeredClient); - } - } - } - } - } - - private static boolean isLoopbackAddress(String host) { - if (!StringUtils.hasText(host)) { - return false; - } - // IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1" - if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) { - return true; - } - // IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255 - String[] ipv4Octets = host.split("\\."); - if (ipv4Octets.length != 4) { - return false; - } - try { - int[] address = new int[ipv4Octets.length]; - for (int i = 0; i < ipv4Octets.length; i++) { - address[i] = Integer.parseInt(ipv4Octets[i]); - } - return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 && address[2] <= 255 - && address[3] >= 1 && address[3] <= 255; - } - catch (NumberFormatException ex) { - return false; - } - } - - private static void throwError(String errorCode, String parameterName, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient) { - throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient); - } - - private static void throwError(String errorCode, String parameterName, String errorUri, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient); - } - - private static void throwError(OAuth2Error error, String parameterName, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient) { - - String redirectUri = StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri()) - ? authorizationCodeRequestAuthentication.getRedirectUri() - : registeredClient.getRedirectUris().iterator().next(); - if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) - && parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) { - redirectUri = null; // Prevent redirects - } - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - authorizationCodeRequestAuthentication.getAuthorizationUri(), - authorizationCodeRequestAuthentication.getClientId(), - (Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri, - authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(), - authorizationCodeRequestAuthentication.getAdditionalParameters()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, - authorizationCodeRequestAuthenticationResult); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java deleted file mode 100644 index 19c371d23..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OAuth2AuthorizationConsent.Builder} and additional information and is used when - * customizing the building of the {@link OAuth2AuthorizationConsent}. - * - * @author Steve Riesenberg - * @author Joe Grandja - * @since 0.2.1 - * @see OAuth2AuthenticationContext - * @see OAuth2AuthorizationConsent - * @see OAuth2AuthorizationConsentAuthenticationProvider#setAuthorizationConsentCustomizer(Consumer) - */ -public final class OAuth2AuthorizationConsentAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OAuth2AuthorizationConsentAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link OAuth2AuthorizationConsent.Builder authorization consent - * builder}. - * @return the {@link OAuth2AuthorizationConsent.Builder} - */ - public OAuth2AuthorizationConsent.Builder getAuthorizationConsent() { - return get(OAuth2AuthorizationConsent.Builder.class); - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Returns the {@link OAuth2Authorization authorization}. - * @return the {@link OAuth2Authorization} - */ - public OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); - } - - /** - * Returns the {@link OAuth2AuthorizationRequest authorization request}. - * @return the {@link OAuth2AuthorizationRequest} - */ - public OAuth2AuthorizationRequest getAuthorizationRequest() { - return get(OAuth2AuthorizationRequest.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OAuth2AuthorizationConsentAuthenticationToken}. - * @param authentication the {@link OAuth2AuthorizationConsentAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OAuth2AuthorizationConsentAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OAuth2AuthorizationConsentAuthenticationContext}. - */ - public static final class Builder - extends AbstractBuilder { - - private Builder(OAuth2AuthorizationConsentAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link OAuth2AuthorizationConsent.Builder authorization consent - * builder}. - * @param authorizationConsent the {@link OAuth2AuthorizationConsent.Builder} - * @return the {@link Builder} for further configuration - */ - public Builder authorizationConsent(OAuth2AuthorizationConsent.Builder authorizationConsent) { - return put(OAuth2AuthorizationConsent.Builder.class, authorizationConsent); - } - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} for further configuration - */ - public Builder registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Sets the {@link OAuth2Authorization authorization}. - * @param authorization the {@link OAuth2Authorization} - * @return the {@link Builder} for further configuration - */ - public Builder authorization(OAuth2Authorization authorization) { - return put(OAuth2Authorization.class, authorization); - } - - /** - * Sets the {@link OAuth2AuthorizationRequest authorization request}. - * @param authorizationRequest the {@link OAuth2AuthorizationRequest} - * @return the {@link Builder} for further configuration - */ - public Builder authorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { - return put(OAuth2AuthorizationRequest.class, authorizationRequest); - } - - /** - * Builds a new {@link OAuth2AuthorizationConsentAuthenticationContext}. - * @return the {@link OAuth2AuthorizationConsentAuthenticationContext} - */ - @Override - public OAuth2AuthorizationConsentAuthenticationContext build() { - Assert.notNull(get(OAuth2AuthorizationConsent.Builder.class), "authorizationConsentBuilder cannot be null"); - Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); - OAuth2Authorization authorization = get(OAuth2Authorization.class); - Assert.notNull(authorization, "authorization cannot be null"); - if (authorization.getAuthorizationGrantType().equals(AuthorizationGrantType.AUTHORIZATION_CODE)) { - Assert.notNull(get(OAuth2AuthorizationRequest.class), "authorizationRequest cannot be null"); - } - return new OAuth2AuthorizationConsentAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java deleted file mode 100644 index 131b58ce6..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization - * Consent used in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2AuthorizationConsentAuthenticationToken - * @see OAuth2AuthorizationConsent - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2AuthorizationConsentService - */ -public final class OAuth2AuthorizationConsentAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2TokenGenerator authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator(); - - private Consumer authorizationConsentCustomizer; - - /** - * Constructs an {@code OAuth2AuthorizationConsentAuthenticationProvider} using the - * provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param authorizationConsentService the authorization consent service - */ - public OAuth2AuthorizationConsentAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, - OAuth2AuthorizationConsentService authorizationConsentService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.authorizationConsentService = authorizationConsentService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (authentication instanceof OAuth2DeviceAuthorizationConsentAuthenticationToken) { - // This is NOT an OAuth 2.0 Authorization Consent for the Authorization Code - // Grant, - // return null and let OAuth2DeviceAuthorizationConsentAuthenticationProvider - // handle it instead - return null; - } - - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication = (OAuth2AuthorizationConsentAuthenticationToken) authentication; - - OAuth2Authorization authorization = this.authorizationService - .findByToken(authorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE); - if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, authorizationConsentAuthentication, - null, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with authorization consent state"); - } - - // The 'in-flight' authorization must be associated to the current principal - Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal(); - if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, authorizationConsentAuthentication, - null, null); - } - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(authorizationConsentAuthentication.getClientId()); - if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, - authorizationConsentAuthentication, registeredClient, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set requestedScopes = authorizationRequest.getScopes(); - Set authorizedScopes = new HashSet<>(authorizationConsentAuthentication.getScopes()); - if (!requestedScopes.containsAll(authorizedScopes)) { - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationConsentAuthentication, - registeredClient, authorizationRequest); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated authorization consent request parameters"); - } - - OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService - .findById(authorization.getRegisteredClientId(), authorization.getPrincipalName()); - Set currentAuthorizedScopes = (currentAuthorizationConsent != null) - ? currentAuthorizationConsent.getScopes() : Collections.emptySet(); - - if (!currentAuthorizedScopes.isEmpty()) { - for (String requestedScope : requestedScopes) { - if (currentAuthorizedScopes.contains(requestedScope)) { - authorizedScopes.add(requestedScope); - } - } - } - - if (!authorizedScopes.isEmpty() && requestedScopes.contains(OidcScopes.OPENID)) { - // 'openid' scope is auto-approved as it does not require consent - authorizedScopes.add(OidcScopes.OPENID); - } - - OAuth2AuthorizationConsent.Builder authorizationConsentBuilder; - if (currentAuthorizationConsent != null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved existing authorization consent"); - } - authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent); - } - else { - authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), - authorization.getPrincipalName()); - } - authorizedScopes.forEach(authorizationConsentBuilder::scope); - - if (this.authorizationConsentCustomizer != null) { - // @formatter:off - OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext = - OAuth2AuthorizationConsentAuthenticationContext.with(authorizationConsentAuthentication) - .authorizationConsent(authorizationConsentBuilder) - .registeredClient(registeredClient) - .authorization(authorization) - .authorizationRequest(authorizationRequest) - .build(); - // @formatter:on - this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Customized authorization consent"); - } - } - - Set authorities = new HashSet<>(); - authorizationConsentBuilder.authorities(authorities::addAll); - - if (authorities.isEmpty()) { - // Authorization consent denied (or revoked) - if (currentAuthorizationConsent != null) { - this.authorizationConsentService.remove(currentAuthorizationConsent); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Revoked authorization consent"); - } - } - this.authorizationService.remove(authorization); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Removed authorization"); - } - throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, - authorizationConsentAuthentication, registeredClient, authorizationRequest); - } - - OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build(); - if (!authorizationConsent.equals(currentAuthorizationConsent)) { - this.authorizationConsentService.save(authorizationConsent); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization consent"); - } - } - - OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationConsentAuthentication, - registeredClient, authorization, authorizedScopes); - OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext); - if (authorizationCode == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the authorization code.", ERROR_URI); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated authorization code"); - } - - OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization) - .authorizedScopes(authorizedScopes) - .token(authorizationCode) - .attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE)) - .build(); - this.authorizationService.save(updatedAuthorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - String redirectUri = authorizationRequest.getRedirectUri(); - if (!StringUtils.hasText(redirectUri)) { - redirectUri = registeredClient.getRedirectUris().iterator().next(); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated authorization consent request"); - } - - return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(), - registeredClient.getClientId(), principal, authorizationCode, redirectUri, - authorizationRequest.getState(), authorizedScopes); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2AuthorizationConsentAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link OAuth2TokenGenerator} that generates the - * {@link OAuth2AuthorizationCode}. - * @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates - * the {@link OAuth2AuthorizationCode} - */ - public void setAuthorizationCodeGenerator( - OAuth2TokenGenerator authorizationCodeGenerator) { - Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null"); - this.authorizationCodeGenerator = authorizationCodeGenerator; - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationConsentAuthenticationContext} containing an - * {@link OAuth2AuthorizationConsent.Builder} and additional context information. - * - *

- * The following context attributes are available: - *

    - *
  • The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization - * consent prior to - * {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.
  • - *
  • The {@link Authentication} of type - * {@link OAuth2AuthorizationConsentAuthenticationToken}.
  • - *
  • The {@link RegisteredClient} associated with the authorization request.
  • - *
  • The {@link OAuth2Authorization} associated with the state token presented in - * the authorization consent request.
  • - *
  • The {@link OAuth2AuthorizationRequest} associated with the authorization - * consent request.
  • - *
- * @param authorizationConsentCustomizer the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationConsentAuthenticationContext} containing an - * {@link OAuth2AuthorizationConsent.Builder} - */ - public void setAuthorizationConsentCustomizer( - Consumer authorizationConsentCustomizer) { - Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null"); - this.authorizationConsentCustomizer = authorizationConsentCustomizer; - } - - private static OAuth2TokenContext createAuthorizationCodeTokenContext( - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, - RegisteredClient registeredClient, OAuth2Authorization authorization, Set authorizedScopes) { - - // @formatter:off - return DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal((Authentication) authorizationConsentAuthentication.getPrincipal()) - .authorization(authorization) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE)) - .authorizedScopes(authorizedScopes) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authorizationConsentAuthentication) - .build(); - // @formatter:on - } - - private static boolean isPrincipalAuthenticated(Authentication principal) { - return principal != null && !AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) - && principal.isAuthenticated(); - } - - private static void throwError(String errorCode, String parameterName, - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); - throwError(error, parameterName, authorizationConsentAuthentication, registeredClient, authorizationRequest); - } - - private static void throwError(OAuth2Error error, String parameterName, - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { - - String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient); - if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) - && (parameterName.equals(OAuth2ParameterNames.CLIENT_ID) - || parameterName.equals(OAuth2ParameterNames.STATE))) { - redirectUri = null; // Prevent redirects - } - - String state = (authorizationRequest != null) ? authorizationRequest.getState() - : authorizationConsentAuthentication.getState(); - Set requestedScopes = (authorizationRequest != null) ? authorizationRequest.getScopes() - : authorizationConsentAuthentication.getScopes(); - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - authorizationConsentAuthentication.getAuthorizationUri(), - authorizationConsentAuthentication.getClientId(), - (Authentication) authorizationConsentAuthentication.getPrincipal(), redirectUri, state, requestedScopes, - null); - - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, - authorizationCodeRequestAuthenticationResult); - } - - private static String resolveRedirectUri(OAuth2AuthorizationRequest authorizationRequest, - RegisteredClient registeredClient) { - if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) { - return authorizationRequest.getRedirectUri(); - } - if (registeredClient != null) { - return registeredClient.getRedirectUris().iterator().next(); - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java deleted file mode 100644 index f278bfeda..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the OAuth 2.0 Authorization Consent used - * in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2AuthorizationConsentAuthenticationProvider - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - */ -public class OAuth2AuthorizationConsentAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -2111287271882598208L; - - private final String authorizationUri; - - private final String clientId; - - private final Authentication principal; - - private final String state; - - private final Set scopes; - - private final Map additionalParameters; - - /** - * Constructs an {@code OAuth2AuthorizationConsentAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the {@code Principal} (Resource Owner) - * @param state the state - * @param scopes the requested (or authorized) scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2AuthorizationConsentAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, String state, @Nullable Set scopes, - @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.hasText(authorizationUri, "authorizationUri cannot be empty"); - Assert.hasText(clientId, "clientId cannot be empty"); - Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(state, "state cannot be empty"); - this.authorizationUri = authorizationUri; - this.clientId = clientId; - this.principal = principal; - this.state = state; - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the authorization URI. - * @return the authorization URI - */ - public String getAuthorizationUri() { - return this.authorizationUri; - } - - /** - * Returns the client identifier. - * @return the client identifier - */ - public String getClientId() { - return this.clientId; - } - - /** - * Returns the state. - * @return the state - */ - public String getState() { - return this.state; - } - - /** - * Returns the requested (or authorized) scope(s). - * @return the requested (or authorized) scope(s), or an empty {@code Set} if not - * available - */ - public Set getScopes() { - return this.scopes; - } - - /** - * Returns the additional parameters. - * @return the additional parameters, or an empty {@code Map} if not available - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java deleted file mode 100644 index 380e50db9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * Base implementation of an {@link Authentication} representing an OAuth 2.0 - * Authorization Grant. - * - * @author Joe Grandja - * @since 0.1.0 - * @see AbstractAuthenticationToken - * @see AuthorizationGrantType - * @see OAuth2ClientAuthenticationToken - * @see Section - * 1.3 Authorization Grant - */ -public class OAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -1715946281123199051L; - - private final AuthorizationGrantType authorizationGrantType; - - private final Authentication clientPrincipal; - - private final Map additionalParameters; - - /** - * Sub-class constructor. - * @param authorizationGrantType the authorization grant type - * @param clientPrincipal the authenticated client principal - * @param additionalParameters the additional parameters - */ - protected OAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType, - Authentication clientPrincipal, @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - this.authorizationGrantType = authorizationGrantType; - this.clientPrincipal = clientPrincipal; - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - } - - /** - * Returns the authorization grant type. - * @return the authorization grant type - */ - public AuthorizationGrantType getGrantType() { - return this.authorizationGrantType; - } - - @Override - public Object getPrincipal() { - return this.clientPrincipal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the additional parameters. - * @return the additional parameters - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java deleted file mode 100644 index 3dd7c99e6..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OAuth2ClientAuthenticationToken} and additional information and is used when - * validating an OAuth 2.0 Client Authentication. - * - * @author Joe Grandja - * @since 1.3 - * @see OAuth2AuthenticationContext - * @see OAuth2ClientAuthenticationToken - * @see X509ClientCertificateAuthenticationProvider#setCertificateVerifier(Consumer) - */ -public final class OAuth2ClientAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OAuth2ClientAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OAuth2ClientAuthenticationToken}. - * @param authentication the {@link OAuth2ClientAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OAuth2ClientAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OAuth2ClientAuthenticationContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OAuth2ClientAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} for further configuration - */ - public Builder registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Builds a new {@link OAuth2ClientAuthenticationContext}. - * @return the {@link OAuth2ClientAuthenticationContext} - */ - @Override - public OAuth2ClientAuthenticationContext build() { - Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); - return new OAuth2ClientAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java deleted file mode 100644 index 3cd8b10fa..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.Transient; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OAuth 2.0 Client Authentication. - * - * @author Joe Grandja - * @author Patryk Kostrzewa - * @author Anoop Garlapati - * @since 0.0.1 - * @see AbstractAuthenticationToken - * @see RegisteredClient - * @see JwtClientAssertionAuthenticationProvider - * @see ClientSecretAuthenticationProvider - * @see PublicClientAuthenticationProvider - */ -@Transient -public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -7150784632941221304L; - - private final String clientId; - - private final RegisteredClient registeredClient; - - private final ClientAuthenticationMethod clientAuthenticationMethod; - - private final Object credentials; - - private final Map additionalParameters; - - /** - * Constructs an {@code OAuth2ClientAuthenticationToken} using the provided - * parameters. - * @param clientId the client identifier - * @param clientAuthenticationMethod the authentication method used by the client - * @param credentials the client credentials - * @param additionalParameters the additional parameters - */ - public OAuth2ClientAuthenticationToken(String clientId, ClientAuthenticationMethod clientAuthenticationMethod, - @Nullable Object credentials, @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.hasText(clientId, "clientId cannot be empty"); - Assert.notNull(clientAuthenticationMethod, "clientAuthenticationMethod cannot be null"); - this.clientId = clientId; - this.registeredClient = null; - this.clientAuthenticationMethod = clientAuthenticationMethod; - this.credentials = credentials; - this.additionalParameters = Collections - .unmodifiableMap((additionalParameters != null) ? additionalParameters : Collections.emptyMap()); - } - - /** - * Constructs an {@code OAuth2ClientAuthenticationToken} using the provided - * parameters. - * @param registeredClient the authenticated registered client - * @param clientAuthenticationMethod the authentication method used by the client - * @param credentials the client credentials - */ - public OAuth2ClientAuthenticationToken(RegisteredClient registeredClient, - ClientAuthenticationMethod clientAuthenticationMethod, @Nullable Object credentials) { - super(Collections.emptyList()); - Assert.notNull(registeredClient, "registeredClient cannot be null"); - Assert.notNull(clientAuthenticationMethod, "clientAuthenticationMethod cannot be null"); - this.clientId = registeredClient.getClientId(); - this.registeredClient = registeredClient; - this.clientAuthenticationMethod = clientAuthenticationMethod; - this.credentials = credentials; - this.additionalParameters = Collections.emptyMap(); - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.clientId; - } - - @Nullable - @Override - public Object getCredentials() { - return this.credentials; - } - - /** - * Returns the authenticated {@link RegisteredClient registered client}, or - * {@code null} if not authenticated. - * @return the authenticated {@link RegisteredClient}, or {@code null} if not - * authenticated - */ - @Nullable - public RegisteredClient getRegisteredClient() { - return this.registeredClient; - } - - /** - * Returns the {@link ClientAuthenticationMethod authentication method} used by the - * client. - * @return the {@link ClientAuthenticationMethod} used by the client - */ - public ClientAuthenticationMethod getClientAuthenticationMethod() { - return this.clientAuthenticationMethod; - } - - /** - * Returns the additional parameters. - * @return the additional parameters - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java deleted file mode 100644 index 7740f496f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OAuth2ClientCredentialsAuthenticationToken} and additional information and is - * used when validating the OAuth 2.0 Client Credentials Grant Request. - * - * @author Adam Pilling - * @since 1.3 - * @see OAuth2AuthenticationContext - * @see OAuth2ClientCredentialsAuthenticationToken - * @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer) - */ -public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OAuth2ClientCredentialsAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OAuth2ClientCredentialsAuthenticationToken}. - * @param authentication the {@link OAuth2ClientCredentialsAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OAuth2ClientCredentialsAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OAuth2ClientCredentialsAuthenticationContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OAuth2ClientCredentialsAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} for further configuration - */ - public Builder registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Builds a new {@link OAuth2ClientCredentialsAuthenticationContext}. - * @return the {@link OAuth2ClientCredentialsAuthenticationContext} - */ - @Override - public OAuth2ClientCredentialsAuthenticationContext build() { - Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); - return new OAuth2ClientCredentialsAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java deleted file mode 100644 index 1e145076a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Client Credentials - * Grant. - * - * @author Alexey Nesterov - * @author Joe Grandja - * @since 0.0.1 - * @see OAuth2ClientCredentialsAuthenticationToken - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see Section 4.4 Client - * Credentials Grant - * @see Section 4.4.2 Access - * Token Request - */ -public final class OAuth2ClientCredentialsAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - private Consumer authenticationValidator = new OAuth2ClientCredentialsAuthenticationValidator(); - - /** - * Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the - * provided parameters. - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - * @since 0.2.3 - */ - public OAuth2ClientCredentialsAuthenticationProvider(OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = (OAuth2ClientCredentialsAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(clientCredentialsAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.CLIENT_CREDENTIALS)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: requested grant_type is not allowed" + " for registered client '%s'", - registeredClient.getId())); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - } - - OAuth2ClientCredentialsAuthenticationContext authenticationContext = OAuth2ClientCredentialsAuthenticationContext - .with(clientCredentialsAuthentication) - .registeredClient(registeredClient) - .build(); - this.authenticationValidator.accept(authenticationContext); - - Set authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes()); - - // Verify the DPoP Proof (if available) - Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(clientCredentialsAuthentication); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated token request parameters"); - } - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(clientPrincipal) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorizedScopes(authorizedScopes) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrant(clientCredentialsAuthentication); - // @formatter:on - if (dPoPProof != null) { - tokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProof); - } - OAuth2TokenContext tokenContext = tokenContextBuilder.build(); - - OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); - if (generatedAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the access token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated access token"); - } - - // @formatter:off - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(clientPrincipal.getName()) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizedScopes(authorizedScopes); - // @formatter:on - - OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder, - generatedAccessToken, tokenContext); - - OAuth2Authorization authorization = authorizationBuilder.build(); - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - // This log is kept separate for consistency with other providers - this.logger.trace("Authenticated token request"); - } - - return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2ClientCredentialsAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2ClientCredentialsAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Client Credentials Grant Request parameters - * associated in the {@link OAuth2ClientCredentialsAuthenticationToken}. The default - * authentication validator is {@link OAuth2ClientCredentialsAuthenticationValidator}. - * - *

- * NOTE: The authentication validator MUST throw - * {@link OAuth2AuthenticationException} if validation fails. - * @param authenticationValidator the {@code Consumer} providing access to the - * {@link OAuth2ClientCredentialsAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Client Credentials Grant Request parameters - * @since 1.3 - */ - public void setAuthenticationValidator( - Consumer authenticationValidator) { - Assert.notNull(authenticationValidator, "authenticationValidator cannot be null"); - this.authenticationValidator = authenticationValidator; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java deleted file mode 100644 index 71c58338b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; - -/** - * An {@link Authentication} implementation used for the OAuth 2.0 Client Credentials - * Grant. - * - * @author Alexey Nesterov - * @since 0.0.1 - * @see OAuth2AuthorizationGrantAuthenticationToken - * @see OAuth2ClientCredentialsAuthenticationProvider - */ -public class OAuth2ClientCredentialsAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { - - private final Set scopes; - - /** - * Constructs an {@code OAuth2ClientCredentialsAuthenticationToken} using the provided - * parameters. - * @param clientPrincipal the authenticated client principal - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2ClientCredentialsAuthenticationToken(Authentication clientPrincipal, @Nullable Set scopes, - @Nullable Map additionalParameters) { - super(AuthorizationGrantType.CLIENT_CREDENTIALS, clientPrincipal, additionalParameters); - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - } - - /** - * Returns the requested scope(s). - * @return the requested scope(s), or an empty {@code Set} if not available - */ - public Set getScopes() { - return this.scopes; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationValidator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationValidator.java deleted file mode 100644 index 7c721dcdf..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationValidator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; - -/** - * A {@code Consumer} providing access to the - * {@link OAuth2ClientCredentialsAuthenticationContext} containing an - * {@link OAuth2ClientCredentialsAuthenticationToken} and is the default - * {@link OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer) - * authentication validator} used for validating specific OAuth 2.0 Client Credentials - * Grant Request parameters. - * - *

- * The default implementation validates - * {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}. If validation fails, an - * {@link OAuth2AuthenticationException} is thrown. - * - * @author Adam Pilling - * @since 1.3 - * @see OAuth2ClientCredentialsAuthenticationContext - * @see OAuth2ClientCredentialsAuthenticationToken - * @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer) - */ -public final class OAuth2ClientCredentialsAuthenticationValidator - implements Consumer { - - private static final Log LOGGER = LogFactory.getLog(OAuth2ClientCredentialsAuthenticationValidator.class); - - /** - * The default validator for - * {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}. - */ - public static final Consumer DEFAULT_SCOPE_VALIDATOR = OAuth2ClientCredentialsAuthenticationValidator::validateScope; - - private final Consumer authenticationValidator = DEFAULT_SCOPE_VALIDATOR; - - @Override - public void accept(OAuth2ClientCredentialsAuthenticationContext authenticationContext) { - this.authenticationValidator.accept(authenticationContext); - } - - private static void validateScope(OAuth2ClientCredentialsAuthenticationContext authenticationContext) { - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = authenticationContext - .getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - - Set requestedScopes = clientCredentialsAuthentication.getScopes(); - Set allowedScopes = registeredClient.getScopes(); - if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(LogMessage.format( - "Invalid request: requested scope is not allowed" + " for registered client '%s'", - registeredClient.getId())); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java deleted file mode 100644 index 45fea1207..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for the Device Authorization Consent - * used in the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2DeviceAuthorizationConsentAuthenticationToken - * @see OAuth2AuthorizationConsent - * @see OAuth2DeviceAuthorizationRequestAuthenticationProvider - * @see OAuth2DeviceVerificationAuthenticationProvider - * @see OAuth2DeviceCodeAuthenticationProvider - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2AuthorizationConsentService - */ -public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2AuthorizationConsentService authorizationConsentService; - - private Consumer authorizationConsentCustomizer; - - /** - * Constructs an {@code OAuth2DeviceAuthorizationConsentAuthenticationProvider} using - * the provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param authorizationConsentService the authorization consent service - */ - public OAuth2DeviceAuthorizationConsentAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, - OAuth2AuthorizationConsentService authorizationConsentService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.authorizationConsentService = authorizationConsentService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2DeviceAuthorizationConsentAuthenticationToken deviceAuthorizationConsentAuthentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) authentication; - - OAuth2Authorization authorization = this.authorizationService - .findByToken(deviceAuthorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE); - if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with device authorization consent state"); - } - - // The authorization must be associated to the current principal - Authentication principal = (Authentication) deviceAuthorizationConsentAuthentication.getPrincipal(); - if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); - } - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(deviceAuthorizationConsentAuthentication.getClientId()); - if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - Set requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); - Set authorizedScopes = new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes()); - if (!requestedScopes.containsAll(authorizedScopes)) { - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated device authorization consent request parameters"); - } - - OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService - .findById(authorization.getRegisteredClientId(), principal.getName()); - Set currentAuthorizedScopes = (currentAuthorizationConsent != null) - ? currentAuthorizationConsent.getScopes() : Collections.emptySet(); - - if (!currentAuthorizedScopes.isEmpty()) { - for (String requestedScope : requestedScopes) { - if (currentAuthorizedScopes.contains(requestedScope)) { - authorizedScopes.add(requestedScope); - } - } - } - - OAuth2AuthorizationConsent.Builder authorizationConsentBuilder; - if (currentAuthorizationConsent != null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved existing authorization consent"); - } - authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent); - } - else { - authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), - principal.getName()); - } - authorizedScopes.forEach(authorizationConsentBuilder::scope); - - if (this.authorizationConsentCustomizer != null) { - // @formatter:off - OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext = - OAuth2AuthorizationConsentAuthenticationContext.with(deviceAuthorizationConsentAuthentication) - .authorizationConsent(authorizationConsentBuilder) - .registeredClient(registeredClient) - .authorization(authorization) - .build(); - // @formatter:on - this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Customized authorization consent"); - } - } - - Set authorities = new HashSet<>(); - authorizationConsentBuilder.authorities(authorities::addAll); - - OAuth2Authorization.Token deviceCodeToken = authorization.getToken(OAuth2DeviceCode.class); - OAuth2Authorization.Token userCodeToken = authorization.getToken(OAuth2UserCode.class); - - if (authorities.isEmpty()) { - // Authorization consent denied (or revoked) - if (currentAuthorizationConsent != null) { - this.authorizationConsentService.remove(currentAuthorizationConsent); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Revoked authorization consent"); - } - } - authorization = OAuth2Authorization.from(authorization) - .invalidate(deviceCodeToken.getToken()) - .invalidate(userCodeToken.getToken()) - .attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE)) - .build(); - this.authorizationService.save(authorization); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Invalidated device code and user code because authorization consent was denied"); - } - throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID); - } - - OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build(); - if (!authorizationConsent.equals(currentAuthorizationConsent)) { - this.authorizationConsentService.save(authorizationConsent); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization consent"); - } - } - - authorization = OAuth2Authorization.from(authorization) - .authorizedScopes(authorizedScopes) - .invalidate(userCodeToken.getToken()) - .attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE)) - .attributes((attrs) -> attrs.remove(OAuth2ParameterNames.SCOPE)) - .build(); - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization with authorized scopes"); - // This log is kept separate for consistency with other providers - this.logger.trace("Authenticated device authorization consent request"); - } - - return new OAuth2DeviceVerificationAuthenticationToken(principal, - deviceAuthorizationConsentAuthentication.getUserCode(), registeredClient.getClientId()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2DeviceAuthorizationConsentAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationConsentAuthenticationContext} containing an - * {@link OAuth2AuthorizationConsent.Builder} and additional context information. - * - *

- * The following context attributes are available: - *

    - *
  • The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization - * consent prior to - * {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.
  • - *
  • The {@link Authentication} of type - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}.
  • - *
  • The {@link RegisteredClient} associated with the device authorization - * request.
  • - *
  • The {@link OAuth2Authorization} associated with the state token presented in - * the device authorization consent request.
  • - *
- * @param authorizationConsentCustomizer the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationConsentAuthenticationContext} containing an - * {@link OAuth2AuthorizationConsent.Builder} - */ - public void setAuthorizationConsentCustomizer( - Consumer authorizationConsentCustomizer) { - Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null"); - this.authorizationConsentCustomizer = authorizationConsentCustomizer; - } - - private static boolean isPrincipalAuthenticated(Authentication principal) { - return principal != null && !AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) - && principal.isAuthenticated(); - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java deleted file mode 100644 index 3e3dd0390..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the Device Authorization Consent used in - * the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AbstractAuthenticationToken - * @see OAuth2DeviceAuthorizationConsentAuthenticationProvider - */ -public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2AuthorizationConsentAuthenticationToken { - - @Serial - private static final long serialVersionUID = 3789252233721827596L; - - private final String userCode; - - private final Set requestedScopes; - - /** - * Constructs an {@code OAuth2DeviceAuthorizationConsentAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the {@code Principal} (Resource Owner) - * @param userCode the user code associated with the device authorization response - * @param state the state - * @param authorizedScopes the authorized scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2DeviceAuthorizationConsentAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, String userCode, String state, @Nullable Set authorizedScopes, - @Nullable Map additionalParameters) { - super(authorizationUri, clientId, principal, state, authorizedScopes, additionalParameters); - Assert.hasText(userCode, "userCode cannot be empty"); - this.userCode = userCode; - this.requestedScopes = null; - setAuthenticated(false); - } - - /** - * Constructs an {@code OAuth2DeviceAuthorizationConsentAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the {@code Principal} (Resource Owner) - * @param userCode the user code associated with the device authorization response - * @param state the state - * @param requestedScopes the requested scope(s) - * @param authorizedScopes the authorized scope(s) - */ - public OAuth2DeviceAuthorizationConsentAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, String userCode, String state, @Nullable Set requestedScopes, - @Nullable Set authorizedScopes) { - super(authorizationUri, clientId, principal, state, authorizedScopes, null); - Assert.hasText(userCode, "userCode cannot be empty"); - this.userCode = userCode; - this.requestedScopes = Collections - .unmodifiableSet((requestedScopes != null) ? new HashSet<>(requestedScopes) : Collections.emptySet()); - setAuthenticated(true); - } - - /** - * Returns the user code. - * @return the user code - */ - public String getUserCode() { - return this.userCode; - } - - /** - * Returns the requested scopes. - * @return the requested scopes - */ - public Set getRequestedScopes() { - return this.requestedScopes; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java deleted file mode 100644 index e32081481..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.time.Instant; -import java.util.Base64; -import java.util.HashSet; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.BytesKeyGenerator; -import org.springframework.security.crypto.keygen.KeyGenerators; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * An {@link AuthenticationProvider} implementation for the Device Authorization Request - * used in the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2DeviceAuthorizationRequestAuthenticationToken - * @see OAuth2DeviceVerificationAuthenticationProvider - * @see OAuth2DeviceAuthorizationConsentAuthenticationProvider - * @see OAuth2DeviceCodeAuthenticationProvider - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see OAuth 2.0 - * Device Authorization Grant - * @see Section 3.1 Device - * Authorization Request - */ -public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); - static final OAuth2TokenType USER_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.USER_CODE); - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private OAuth2TokenGenerator deviceCodeGenerator = new OAuth2DeviceCodeGenerator(); - - private OAuth2TokenGenerator userCodeGenerator = new OAuth2UserCodeGenerator(); - - /** - * Constructs an {@code OAuth2DeviceAuthorizationRequestAuthenticationProvider} using - * the provided parameters. - * @param authorizationService the authorization service - */ - public OAuth2DeviceAuthorizationRequestAuthenticationProvider(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.authorizationService = authorizationService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthentication = (OAuth2DeviceAuthorizationRequestAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(deviceAuthorizationRequestAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.DEVICE_CODE)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: requested grant_type is not allowed" + " for registered client '%s'", - registeredClient.getId())); - } - throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID); - } - - Set requestedScopes = deviceAuthorizationRequestAuthentication.getScopes(); - if (!CollectionUtils.isEmpty(requestedScopes)) { - for (String requestedScope : requestedScopes) { - if (!registeredClient.getScopes().contains(requestedScope)) { - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); - } - } - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated device authorization request parameters"); - } - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(clientPrincipal) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .authorizationGrant(deviceAuthorizationRequestAuthentication); - // @formatter:on - - // Generate a high-entropy string to use as the device code - OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(DEVICE_CODE_TOKEN_TYPE).build(); - OAuth2DeviceCode deviceCode = this.deviceCodeGenerator.generate(tokenContext); - if (deviceCode == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the device code.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated device code"); - } - - // Generate a low-entropy string to use as the user code - tokenContext = tokenContextBuilder.tokenType(USER_CODE_TOKEN_TYPE).build(); - OAuth2UserCode userCode = this.userCodeGenerator.generate(tokenContext); - if (userCode == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the user code.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated user code"); - } - - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(clientPrincipal.getName()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(deviceCode) - .token(userCode) - .attribute(OAuth2ParameterNames.SCOPE, new HashSet<>(requestedScopes)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated device authorization request"); - } - - return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, requestedScopes, deviceCode, - userCode); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2DeviceAuthorizationRequestAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2DeviceCode}. - * @param deviceCodeGenerator the {@link OAuth2TokenGenerator} that generates the - * {@link OAuth2DeviceCode} - */ - public void setDeviceCodeGenerator(OAuth2TokenGenerator deviceCodeGenerator) { - Assert.notNull(deviceCodeGenerator, "deviceCodeGenerator cannot be null"); - this.deviceCodeGenerator = deviceCodeGenerator; - } - - /** - * Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2UserCode}. - * @param userCodeGenerator the {@link OAuth2TokenGenerator} that generates the - * {@link OAuth2UserCode} - */ - public void setUserCodeGenerator(OAuth2TokenGenerator userCodeGenerator) { - Assert.notNull(userCodeGenerator, "userCodeGenerator cannot be null"); - this.userCodeGenerator = userCodeGenerator; - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - private static final class OAuth2DeviceCodeGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator deviceCodeGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - @Nullable - @Override - public OAuth2DeviceCode generate(OAuth2TokenContext context) { - if (context.getTokenType() == null - || !OAuth2ParameterNames.DEVICE_CODE.equals(context.getTokenType().getValue())) { - return null; - } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(context.getRegisteredClient().getTokenSettings().getDeviceCodeTimeToLive()); - return new OAuth2DeviceCode(this.deviceCodeGenerator.generateKey(), issuedAt, expiresAt); - } - - } - - private static final class UserCodeStringKeyGenerator implements StringKeyGenerator { - - // @formatter:off - private static final char[] VALID_CHARS = { - 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', - 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z' - }; - // @formatter:on - - private final BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom(8); - - @Override - public String generateKey() { - byte[] bytes = this.keyGenerator.generateKey(); - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - int offset = Math.abs(b % 20); - sb.append(VALID_CHARS[offset]); - } - sb.insert(4, '-'); - return sb.toString(); - } - - } - - private static final class OAuth2UserCodeGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator userCodeGenerator = new UserCodeStringKeyGenerator(); - - @Nullable - @Override - public OAuth2UserCode generate(OAuth2TokenContext context) { - if (context.getTokenType() == null - || !OAuth2ParameterNames.USER_CODE.equals(context.getTokenType().getValue())) { - return null; - } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(context.getRegisteredClient().getTokenSettings().getDeviceCodeTimeToLive()); - return new OAuth2UserCode(this.userCodeGenerator.generateKey(), issuedAt, expiresAt); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java deleted file mode 100644 index b301223cc..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the Device Authorization Request used in - * the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AbstractAuthenticationToken - * @see OAuth2ClientAuthenticationToken - * @see OAuth2DeviceAuthorizationRequestAuthenticationProvider - */ -public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -561059025431630645L; - - private final Authentication clientPrincipal; - - private final String authorizationUri; - - private final Set scopes; - - private final OAuth2DeviceCode deviceCode; - - private final OAuth2UserCode userCode; - - private final Map additionalParameters; - - /** - * Constructs an {@code OAuth2DeviceAuthorizationRequestAuthenticationToken} using the - * provided parameters. - * @param clientPrincipal the authenticated client principal - * @param authorizationUri the authorization {@code URI} - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2DeviceAuthorizationRequestAuthenticationToken(Authentication clientPrincipal, String authorizationUri, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - Assert.hasText(authorizationUri, "authorizationUri cannot be empty"); - this.clientPrincipal = clientPrincipal; - this.authorizationUri = authorizationUri; - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - this.deviceCode = null; - this.userCode = null; - } - - /** - * Constructs an {@code OAuth2DeviceAuthorizationRequestAuthenticationToken} using the - * provided parameters. - * @param clientPrincipal the authenticated client principal - * @param scopes the requested scope(s) - * @param deviceCode the {@link OAuth2DeviceCode} - * @param userCode the {@link OAuth2UserCode} - */ - public OAuth2DeviceAuthorizationRequestAuthenticationToken(Authentication clientPrincipal, - @Nullable Set scopes, OAuth2DeviceCode deviceCode, OAuth2UserCode userCode) { - super(Collections.emptyList()); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - Assert.notNull(deviceCode, "deviceCode cannot be null"); - Assert.notNull(userCode, "userCode cannot be null"); - this.clientPrincipal = clientPrincipal; - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - this.deviceCode = deviceCode; - this.userCode = userCode; - this.authorizationUri = null; - this.additionalParameters = Collections.emptyMap(); - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.clientPrincipal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the authorization {@code URI}. - * @return the authorization {@code URI} - */ - public String getAuthorizationUri() { - return this.authorizationUri; - } - - /** - * Returns the requested scope(s). - * @return the requested scope(s) - */ - public Set getScopes() { - return this.scopes; - } - - /** - * Returns the device code. - * @return the device code - */ - public OAuth2DeviceCode getDeviceCode() { - return this.deviceCode; - } - - /** - * Returns the user code. - * @return the user code - */ - public OAuth2UserCode getUserCode() { - return this.userCode; - } - - /** - * Returns the additional parameters. - * @return the additional parameters - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java deleted file mode 100644 index cddd2f47a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -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.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for the Device Access Token Request - * used in the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2DeviceCodeAuthenticationToken - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2DeviceAuthorizationRequestAuthenticationProvider - * @see OAuth2DeviceVerificationAuthenticationProvider - * @see OAuth2DeviceAuthorizationConsentAuthenticationProvider - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see OAuth 2.0 - * Device Authorization Grant - * @see Section 3.4 Device Access - * Token Request - * @see Section 3.5 Device Access - * Token Response - */ -public final class OAuth2DeviceCodeAuthenticationProvider implements AuthenticationProvider { - - private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final String DEVICE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc8628#section-3.5"; - static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); - static final String EXPIRED_TOKEN = "expired_token"; - static final String AUTHORIZATION_PENDING = "authorization_pending"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - /** - * Constructs an {@code OAuth2DeviceCodeAuthenticationProvider} using the provided - * parameters. - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - */ - public OAuth2DeviceCodeAuthenticationProvider(OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2DeviceCodeAuthenticationToken deviceCodeAuthentication = (OAuth2DeviceCodeAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(deviceCodeAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2Authorization authorization = this.authorizationService - .findByToken(deviceCodeAuthentication.getDeviceCode(), DEVICE_CODE_TOKEN_TYPE); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with device code"); - } - - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); - OAuth2Authorization.Token deviceCode = authorization.getToken(OAuth2DeviceCode.class); - - if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { - if (!deviceCode.isInvalidated()) { - // Invalidate the device code given that a different client is attempting - // to use it - authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'", - authorization.getRegisteredClientId())); - } - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - // In https://www.rfc-editor.org/rfc/rfc8628.html#section-3.5, - // the following error codes are defined: - - // expired_token - // The "device_code" has expired, and the device authorization - // session has concluded. The client MAY commence a new device - // authorization request but SHOULD wait for user interaction before - // restarting to avoid unnecessary polling. - if (deviceCode.isExpired()) { - if (!deviceCode.isInvalidated()) { - // Invalidate the device code - authorization = OAuth2Authorization.from(authorization).invalidate(deviceCode.getToken()).build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format("Invalidated device code used by registered client '%s'", - authorization.getRegisteredClientId())); - } - } - OAuth2Error error = new OAuth2Error(EXPIRED_TOKEN, null, DEVICE_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - // authorization_pending - // The authorization request is still pending as the end user hasn't - // yet completed the user-interaction steps (Section 3.3). The - // client SHOULD repeat the access token request to the token - // endpoint (a process known as polling). Before each new request, - // the client MUST wait at least the number of seconds specified by - // the "interval" parameter of the device authorization response (see - // Section 3.2), or 5 seconds if none was provided, and respect any - // increase in the polling interval required by the "slow_down" - // error. - if (!userCode.isInvalidated()) { - OAuth2Error error = new OAuth2Error(AUTHORIZATION_PENDING, null, DEVICE_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - // slow_down - // A variant of "authorization_pending", the authorization request is - // still pending and polling should continue, but the interval MUST - // be increased by 5 seconds for this and all subsequent requests. - // NOTE: This error is not handled in the framework. - - // access_denied - // The authorization request was denied. - if (deviceCode.isInvalidated()) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED, null, DEVICE_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - // Verify the DPoP Proof (if available) - Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(deviceCodeAuthentication); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated device token request parameters"); - } - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(authorization.getAttribute(Principal.class.getName())) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .authorizationGrant(deviceCodeAuthentication); - // @formatter:on - if (dPoPProof != null) { - tokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProof); - } - - // @formatter:off - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization) - // Invalidate the device code as it can only be used (successfully) once - .invalidate(deviceCode.getToken()); - // @formatter:on - - // ----- Access token ----- - OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); - OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); - if (generatedAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the access token.", DEFAULT_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated access token"); - } - - OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder, - generatedAccessToken, tokenContext); - - // ----- Refresh token ----- - OAuth2RefreshToken refreshToken = null; - if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) { - tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); - OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); - if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the refresh token.", DEFAULT_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated refresh token"); - } - - refreshToken = (OAuth2RefreshToken) generatedRefreshToken; - authorizationBuilder.refreshToken(refreshToken); - } - - authorization = authorizationBuilder.build(); - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated device token request"); - } - - return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2DeviceCodeAuthenticationToken.class.isAssignableFrom(authentication); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java deleted file mode 100644 index 5ca6196a9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.authentication; - -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the Device Access Token Request used in - * the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2AuthorizationGrantAuthenticationToken - * @see OAuth2DeviceCodeAuthenticationProvider - */ -public class OAuth2DeviceCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { - - private final String deviceCode; - - /** - * Constructs an {@code OAuth2DeviceCodeAuthenticationToken} using the provided - * parameters. - * @param deviceCode the device code - * @param clientPrincipal the authenticated client principal - * @param additionalParameters the additional parameters - */ - public OAuth2DeviceCodeAuthenticationToken(String deviceCode, Authentication clientPrincipal, - @Nullable Map additionalParameters) { - super(AuthorizationGrantType.DEVICE_CODE, clientPrincipal, additionalParameters); - Assert.hasText(deviceCode, "deviceCode cannot be empty"); - this.deviceCode = deviceCode; - } - - /** - * Returns the device code. - * @return the device code - */ - public String getDeviceCode() { - return this.deviceCode; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java deleted file mode 100644 index b631c088d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.util.Base64; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for the Device Verification Request - * (submission of the user code) used in the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2DeviceVerificationAuthenticationToken - * @see OAuth2DeviceAuthorizationRequestAuthenticationProvider - * @see OAuth2DeviceAuthorizationConsentAuthenticationProvider - * @see OAuth2DeviceCodeAuthenticationProvider - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2AuthorizationConsentService - * @see OAuth 2.0 - * Device Authorization Grant - * @see Section 3.3 User - * Interaction - */ -public final class OAuth2DeviceVerificationAuthenticationProvider implements AuthenticationProvider { - - static final OAuth2TokenType USER_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.USER_CODE); - - private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( - Base64.getUrlEncoder()); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2AuthorizationConsentService authorizationConsentService; - - /** - * Constructs an {@code OAuth2DeviceVerificationAuthenticationProvider} using the - * provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param authorizationConsentService the authorization consent service - */ - public OAuth2DeviceVerificationAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, - OAuth2AuthorizationConsentService authorizationConsentService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.authorizationConsentService = authorizationConsentService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2DeviceVerificationAuthenticationToken deviceVerificationAuthentication = (OAuth2DeviceVerificationAuthenticationToken) authentication; - - OAuth2Authorization authorization = this.authorizationService - .findByToken(deviceVerificationAuthentication.getUserCode(), USER_CODE_TOKEN_TYPE); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with user code"); - } - - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); - if (!userCode.isActive()) { - if (!userCode.isInvalidated()) { - authorization = OAuth2Authorization.from(authorization).invalidate(userCode.getToken()).build(); - this.authorizationService.save(authorization); - if (this.logger.isWarnEnabled()) { - this.logger.warn(LogMessage.format("Invalidated user code used by registered client '%s'", - authorization.getRegisteredClientId())); - } - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - Authentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal(); - if (!isPrincipalAuthenticated(principal)) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not authenticate device verification request since principal not authenticated"); - } - // Return the device verification request as-is where isAuthenticated() is - // false - return deviceVerificationAuthentication; - } - - RegisteredClient registeredClient = this.registeredClientRepository - .findById(authorization.getRegisteredClientId()); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - Set requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); - - OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService - .findById(registeredClient.getId(), principal.getName()); - - if (requiresAuthorizationConsent(requestedScopes, currentAuthorizationConsent)) { - String state = DEFAULT_STATE_GENERATOR.generateKey(); - authorization = OAuth2Authorization.from(authorization) - .principalName(principal.getName()) - .attribute(Principal.class.getName(), principal) - .attribute(OAuth2ParameterNames.STATE, state) - .build(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated device authorization consent state"); - } - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - Set currentAuthorizedScopes = (currentAuthorizationConsent != null) - ? currentAuthorizationConsent.getScopes() : null; - - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerContextHolder.getContext() - .getAuthorizationServerSettings(); - String deviceVerificationUri = authorizationServerSettings.getDeviceVerificationEndpoint(); - - return new OAuth2DeviceAuthorizationConsentAuthenticationToken(deviceVerificationUri, - registeredClient.getClientId(), principal, deviceVerificationAuthentication.getUserCode(), state, - requestedScopes, currentAuthorizedScopes); - } - - // @formatter:off - authorization = OAuth2Authorization.from(authorization) - .principalName(principal.getName()) - .authorizedScopes(requestedScopes) - .invalidate(userCode.getToken()) - .attribute(Principal.class.getName(), principal) - .attributes((attributes) -> attributes.remove(OAuth2ParameterNames.SCOPE)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization with authorized scopes"); - // This log is kept separate for consistency with other providers - this.logger.trace("Authenticated device verification request"); - } - - return new OAuth2DeviceVerificationAuthenticationToken(principal, - deviceVerificationAuthentication.getUserCode(), registeredClient.getClientId()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2DeviceVerificationAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static boolean requiresAuthorizationConsent(Set requestedScopes, - OAuth2AuthorizationConsent authorizationConsent) { - - if (authorizationConsent != null && authorizationConsent.getScopes().containsAll(requestedScopes)) { - return false; - } - - return true; - } - - private static boolean isPrincipalAuthenticated(Authentication principal) { - return principal != null && !AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) - && principal.isAuthenticated(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java deleted file mode 100644 index 58f1f3642..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the Device Verification Request - * (submission of the user code) used in the OAuth 2.0 Device Authorization Grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AbstractAuthenticationToken - * @see OAuth2DeviceVerificationAuthenticationProvider - */ -public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -2164261941629756913L; - - private final Authentication principal; - - private final String userCode; - - private final Map additionalParameters; - - private final String clientId; - - /** - * Constructs an {@code OAuth2DeviceVerificationAuthenticationToken} using the - * provided parameters. - * @param principal the {@code Principal} (Resource Owner) - * @param userCode the user code associated with the device authorization response - * @param additionalParameters the additional parameters - */ - public OAuth2DeviceVerificationAuthenticationToken(Authentication principal, String userCode, - @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(userCode, "userCode cannot be empty"); - this.principal = principal; - this.userCode = userCode; - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - this.clientId = null; - } - - /** - * Constructs an {@code OAuth2DeviceVerificationAuthenticationToken} using the - * provided parameters. - * @param principal the {@code Principal} (Resource Owner) - * @param userCode the user code associated with the device authorization response - * @param clientId the client identifier - */ - public OAuth2DeviceVerificationAuthenticationToken(Authentication principal, String userCode, String clientId) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(userCode, "userCode cannot be empty"); - Assert.hasText(clientId, "clientId cannot be empty"); - this.principal = principal; - this.userCode = userCode; - this.clientId = clientId; - this.additionalParameters = Collections.emptyMap(); - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the user code. - * @return the user code - */ - public String getUserCode() { - return this.userCode; - } - - /** - * Returns the additional parameters. - * @return the additional parameters, or an empty {@code Map} if not available - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - - /** - * Returns the client identifier. - * @return the client identifier - */ - public String getClientId() { - return this.clientId; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java deleted file mode 100644 index e41cb2a0f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Pushed Authorization - * Request used in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 1.5 - * @see OAuth2PushedAuthorizationRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2AuthorizationCodeRequestAuthenticationValidator - * @see OAuth2AuthorizationService - * @see Section 2.1 Pushed - * Authorization Request - * @see Section 2.2 Pushed - * Authorization Response - */ -public final class OAuth2PushedAuthorizationRequestAuthenticationProvider implements AuthenticationProvider { - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private Consumer authenticationValidator = new OAuth2AuthorizationCodeRequestAuthenticationValidator(); - - /** - * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationProvider} using - * the provided parameters. - * @param authorizationService the authorization service - */ - public OAuth2PushedAuthorizationRequestAuthenticationProvider(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.authorizationService = authorizationService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthentication = (OAuth2PushedAuthorizationRequestAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(pushedAuthorizationRequestAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = OAuth2AuthorizationCodeRequestAuthenticationContext - .with(toAuthorizationCodeRequestAuthentication(pushedAuthorizationRequestAuthentication)) - .registeredClient(registeredClient) - .build(); - - // grant_type - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR - .accept(authenticationContext); - - // redirect_uri and scope - this.authenticationValidator.accept(authenticationContext); - - // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR - .accept(authenticationContext); - - // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) - OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR.accept(authenticationContext); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated pushed authorization request parameters"); - } - - // @formatter:off - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(pushedAuthorizationRequestAuthentication.getAuthorizationUri()) - .clientId(registeredClient.getClientId()) - .redirectUri(pushedAuthorizationRequestAuthentication.getRedirectUri()) - .scopes(pushedAuthorizationRequestAuthentication.getScopes()) - .state(pushedAuthorizationRequestAuthentication.getState()) - .additionalParameters(pushedAuthorizationRequestAuthentication.getAdditionalParameters()) - .build(); - // @formatter:on - - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri - .create(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated pushed authorization request uri"); - } - - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(clientPrincipal.getName()) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest) - .attribute(OAuth2ParameterNames.STATE, pushedAuthorizationRequestUri.getState()) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated pushed authorization request"); - } - - return new OAuth2PushedAuthorizationRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(), - authorizationRequest.getClientId(), clientPrincipal, pushedAuthorizationRequestUri.getRequestUri(), - pushedAuthorizationRequestUri.getExpiresAt(), authorizationRequest.getRedirectUri(), - authorizationRequest.getState(), authorizationRequest.getScopes()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2PushedAuthorizationRequestAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Pushed Authorization Request parameters associated in - * the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. The default - * authentication validator is - * {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}. - * - *

- * NOTE: The authentication validator MUST throw - * {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails. - * @param authenticationValidator the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for - * validating specific OAuth 2.0 Pushed Authorization Request parameters - */ - public void setAuthenticationValidator( - Consumer authenticationValidator) { - Assert.notNull(authenticationValidator, "authenticationValidator cannot be null"); - this.authenticationValidator = authenticationValidator; - } - - private static OAuth2AuthorizationCodeRequestAuthenticationToken toAuthorizationCodeRequestAuthentication( - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationCodeRequestAuthentication) { - return new OAuth2AuthorizationCodeRequestAuthenticationToken( - pushedAuthorizationCodeRequestAuthentication.getAuthorizationUri(), - pushedAuthorizationCodeRequestAuthentication.getClientId(), - (Authentication) pushedAuthorizationCodeRequestAuthentication.getPrincipal(), - pushedAuthorizationCodeRequestAuthentication.getRedirectUri(), - pushedAuthorizationCodeRequestAuthentication.getState(), - pushedAuthorizationCodeRequestAuthentication.getScopes(), - pushedAuthorizationCodeRequestAuthentication.getAdditionalParameters()); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java deleted file mode 100644 index e2a2fbc4b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.time.Instant; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation for the OAuth 2.0 Pushed Authorization Request - * used in the Authorization Code Grant. - * - * @author Joe Grandja - * @since 1.5 - * @see OAuth2PushedAuthorizationRequestAuthenticationProvider - */ -public class OAuth2PushedAuthorizationRequestAuthenticationToken - extends AbstractOAuth2AuthorizationCodeRequestAuthenticationToken { - - @Serial - private static final long serialVersionUID = 7330534287786569644L; - - private final String requestUri; - - private final Instant requestUriExpiresAt; - - /** - * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the authenticated client principal - * @param redirectUri the redirect uri - * @param state the state - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, @Nullable String redirectUri, @Nullable String state, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(authorizationUri, clientId, principal, redirectUri, state, scopes, additionalParameters); - this.requestUri = null; - this.requestUriExpiresAt = null; - } - - /** - * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationToken} using the - * provided parameters. - * @param authorizationUri the authorization URI - * @param clientId the client identifier - * @param principal the authenticated client principal - * @param requestUri the {@code request_uri} corresponding to the authorization - * request posted - * @param requestUriExpiresAt the expiration time on or after which the - * {@code request_uri} MUST NOT be accepted - * @param redirectUri the redirect uri - * @param state the state - * @param scopes the authorized scope(s) - */ - public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationUri, String clientId, - Authentication principal, String requestUri, Instant requestUriExpiresAt, @Nullable String redirectUri, - @Nullable String state, @Nullable Set scopes) { - super(authorizationUri, clientId, principal, redirectUri, state, scopes, null); - Assert.hasText(requestUri, "requestUri cannot be empty"); - Assert.notNull(requestUriExpiresAt, "requestUriExpiresAt cannot be null"); - this.requestUri = requestUri; - this.requestUriExpiresAt = requestUriExpiresAt; - setAuthenticated(true); - } - - /** - * Returns the {@code request_uri} corresponding to the authorization request posted. - * @return the {@code request_uri} corresponding to the authorization request posted - */ - @Nullable - public String getRequestUri() { - return this.requestUri; - } - - /** - * Returns the expiration time on or after which the {@code request_uri} MUST NOT be - * accepted. - * @return the expiration time on or after which the {@code request_uri} MUST NOT be - * accepted - */ - @Nullable - public Instant getRequestUriExpiresAt() { - return this.requestUriExpiresAt; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java deleted file mode 100644 index 766418620..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.time.Instant; -import java.util.Base64; - -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; - -/** - * A representation of a {@code request_uri} used in OAuth 2.0 Pushed Authorization - * Requests. - * - * @author Joe Grandja - * @since 1.5 - */ -final class OAuth2PushedAuthorizationRequestUri { - - private static final String REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:"; - - private static final String REQUEST_URI_DELIMITER = "___"; - - private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( - Base64.getUrlEncoder()); - - private String requestUri; - - private String state; - - private Instant expiresAt; - - static OAuth2PushedAuthorizationRequestUri create() { - return create(Instant.now().plusSeconds(300)); - } - - static OAuth2PushedAuthorizationRequestUri create(Instant expiresAt) { - String state = DEFAULT_STATE_GENERATOR.generateKey(); - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri(); - pushedAuthorizationRequestUri.requestUri = REQUEST_URI_PREFIX + state + REQUEST_URI_DELIMITER - + expiresAt.toEpochMilli(); - pushedAuthorizationRequestUri.state = state + REQUEST_URI_DELIMITER + expiresAt.toEpochMilli(); - pushedAuthorizationRequestUri.expiresAt = expiresAt; - return pushedAuthorizationRequestUri; - } - - static OAuth2PushedAuthorizationRequestUri parse(String requestUri) { - int stateStartIndex = REQUEST_URI_PREFIX.length(); - int expiresAtStartIndex = requestUri.indexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length(); - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri(); - pushedAuthorizationRequestUri.requestUri = requestUri; - pushedAuthorizationRequestUri.state = requestUri.substring(stateStartIndex); - pushedAuthorizationRequestUri.expiresAt = Instant - .ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex))); - return pushedAuthorizationRequestUri; - } - - String getRequestUri() { - return this.requestUri; - } - - String getState() { - return this.state; - } - - Instant getExpiresAt() { - return this.expiresAt; - } - - private OAuth2PushedAuthorizationRequestUri() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java deleted file mode 100644 index 9d20316d8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import com.nimbusds.jose.jwk.JWK; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -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.OAuth2Token; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Refresh Token Grant. - * - * @author Alexey Nesterov - * @author Joe Grandja - * @author Anoop Garlapati - * @since 0.0.3 - * @see OAuth2RefreshTokenAuthenticationToken - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see Section 1.5 Refresh Token - * Grant - * @see Section 6 Refreshing an - * Access Token - */ -public final class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - /** - * Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided - * parameters. - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - * @since 0.2.3 - */ - public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication = (OAuth2RefreshTokenAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OAuth2Authorization authorization = this.authorizationService - .findByToken(refreshTokenAuthentication.getRefreshToken(), OAuth2TokenType.REFRESH_TOKEN); - if (authorization == null) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Invalid request: refresh_token is invalid"); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with refresh token"); - } - - if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: requested grant_type is not allowed" + " for registered client '%s'", - registeredClient.getId())); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - } - - OAuth2Authorization.Token refreshToken = authorization.getRefreshToken(); - if (!refreshToken.isActive()) { - // As per https://tools.ietf.org/html/rfc6749#section-5.2 - // invalid_grant: The provided authorization grant (e.g., authorization code, - // resource owner credentials) or refresh token is invalid, expired, revoked - // [...]. - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format( - "Invalid request: refresh_token is not active" + " for registered client '%s'", - registeredClient.getId())); - } - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - // As per https://tools.ietf.org/html/rfc6749#section-6 - // The requested scope MUST NOT include any scope not originally granted by the - // resource owner, - // and if omitted is treated as equal to the scope originally granted by the - // resource owner. - Set scopes = refreshTokenAuthentication.getScopes(); - Set authorizedScopes = authorization.getAuthorizedScopes(); - if (!authorizedScopes.containsAll(scopes)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); - } - - // Verify the DPoP Proof (if available) - Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(refreshTokenAuthentication); - - if (dPoPProof != null - && clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { - // For public clients, verify the DPoP Proof public key is same as (current) - // access token public key binding - Map accessTokenClaims = authorization.getAccessToken().getClaims(); - verifyDPoPProofPublicKey(dPoPProof, () -> accessTokenClaims); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated token request parameters"); - } - - if (scopes.isEmpty()) { - scopes = authorizedScopes; - } - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(authorization.getAttribute(Principal.class.getName())) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorization(authorization) - .authorizedScopes(scopes) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrant(refreshTokenAuthentication); - // @formatter:on - if (dPoPProof != null) { - tokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProof); - } - - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization); - - // ----- Access token ----- - OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); - OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); - if (generatedAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the access token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated access token"); - } - - OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder, - generatedAccessToken, tokenContext); - - // ----- Refresh token ----- - OAuth2RefreshToken currentRefreshToken = refreshToken.getToken(); - if (!registeredClient.getTokenSettings().isReuseRefreshTokens()) { - // @formatter:off - tokenContext = tokenContextBuilder - .tokenType(OAuth2TokenType.REFRESH_TOKEN) - .authorization(authorizationBuilder.build()) // Refresh token generator/customizer may need access to the access token - .build(); - // @formatter:on - OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); - if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the refresh token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated refresh token"); - } - - currentRefreshToken = (OAuth2RefreshToken) generatedRefreshToken; - authorizationBuilder.refreshToken(currentRefreshToken); - } - - // ----- ID token ----- - OidcIdToken idToken; - if (authorizedScopes.contains(OidcScopes.OPENID)) { - // @formatter:off - tokenContext = tokenContextBuilder - .tokenType(ID_TOKEN_TOKEN_TYPE) - .authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token - .build(); - // @formatter:on - OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext); - if (!(generatedIdToken instanceof Jwt)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the ID token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated id token"); - } - - idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(), - generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims()); - authorizationBuilder.token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())); - } - else { - idToken = null; - } - - authorization = authorizationBuilder.build(); - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - Map additionalParameters = Collections.emptyMap(); - if (idToken != null) { - additionalParameters = new HashMap<>(); - additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue()); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated token request"); - } - - return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, - currentRefreshToken, additionalParameters); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static void verifyDPoPProofPublicKey(Jwt dPoPProof, ClaimAccessor accessTokenClaims) { - JWK jwk = null; - @SuppressWarnings("unchecked") - Map jwkJson = (Map) dPoPProof.getHeaders().get("jwk"); - try { - jwk = JWK.parse(jwkJson); - } - catch (Exception ignored) { - } - if (jwk == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, - "jwk header is missing or invalid.", null); - throw new OAuth2AuthenticationException(error); - } - - String jwkThumbprint; - try { - jwkThumbprint = jwk.computeThumbprint().toString(); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, - "Failed to compute SHA-256 Thumbprint for jwk.", null); - throw new OAuth2AuthenticationException(error); - } - - String jwkThumbprintClaim = null; - Map confirmationMethodClaim = accessTokenClaims.getClaimAsMap("cnf"); - if (!CollectionUtils.isEmpty(confirmationMethodClaim) && confirmationMethodClaim.containsKey("jkt")) { - jwkThumbprintClaim = (String) confirmationMethodClaim.get("jkt"); - } - if (jwkThumbprintClaim == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, "jkt claim is missing.", null); - throw new OAuth2AuthenticationException(error); - } - - if (!jwkThumbprint.equals(jwkThumbprintClaim)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, "jwk header is invalid.", null); - throw new OAuth2AuthenticationException(error); - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java deleted file mode 100644 index a216854c8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for the OAuth 2.0 Refresh Token Grant. - * - * @author Alexey Nesterov - * @since 0.0.3 - * @see OAuth2AuthorizationGrantAuthenticationToken - * @see OAuth2RefreshTokenAuthenticationProvider - */ -public class OAuth2RefreshTokenAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { - - private final String refreshToken; - - private final Set scopes; - - /** - * Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided - * parameters. - * @param refreshToken the refresh token - * @param clientPrincipal the authenticated client principal - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(AuthorizationGrantType.REFRESH_TOKEN, clientPrincipal, additionalParameters); - Assert.hasText(refreshToken, "refreshToken cannot be empty"); - this.refreshToken = refreshToken; - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - } - - /** - * Returns the refresh token. - * @return the refresh token - */ - public String getRefreshToken() { - return this.refreshToken; - } - - /** - * Returns the requested scope(s). - * @return the requested scope(s), or an empty {@code Set} if not available - */ - public Set getScopes() { - return this.scopes; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java deleted file mode 100644 index 475b7620e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; - -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.util.Assert; - -/** - * A {@link ClaimAccessor} used for the OAuth 2.0 Token Exchange Grant to represent an - * actor in a {@link OAuth2TokenExchangeCompositeAuthenticationToken} (e.g. the - * "delegation" use case). - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2TokenExchangeCompositeAuthenticationToken - */ -public final class OAuth2TokenExchangeActor implements ClaimAccessor { - - private final Map claims; - - public OAuth2TokenExchangeActor(Map claims) { - Assert.notNull(claims, "claims cannot be null"); - this.claims = Collections.unmodifiableMap(claims); - } - - @Override - public Map getClaims() { - return this.claims; - } - - public String getIssuer() { - return getClaimAsString(OAuth2TokenClaimNames.ISS); - } - - public String getSubject() { - return getClaimAsString(OAuth2TokenClaimNames.SUB); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof OAuth2TokenExchangeActor other)) { - return false; - } - return Objects.equals(this.claims, other.claims); - } - - @Override - public int hashCode() { - return Objects.hash(this.claims); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java deleted file mode 100644 index e97c77616..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for the OAuth 2.0 Token Exchange - * Grant. - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2TokenExchangeAuthenticationToken - * @see OAuth2AccessTokenAuthenticationToken - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see Section 1 Introduction - * @see Section 2.1 Request - */ -public final class OAuth2TokenExchangeAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String MAY_ACT = "may_act"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - /** - * Constructs an {@code OAuth2TokenExchangeAuthenticationProvider} using the provided - * parameters. - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - */ - public OAuth2TokenExchangeAuthenticationProvider(OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = (OAuth2TokenExchangeAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(tokenExchangeAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.TOKEN_EXCHANGE)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - } - - if (JWT_TOKEN_TYPE_VALUE.equals(tokenExchangeAuthentication.getRequestedTokenType()) - && !OAuth2TokenFormat.SELF_CONTAINED - .equals(registeredClient.getTokenSettings().getAccessTokenFormat())) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - OAuth2Authorization subjectAuthorization = this.authorizationService - .findByToken(tokenExchangeAuthentication.getSubjectToken(), OAuth2TokenType.ACCESS_TOKEN); - if (subjectAuthorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with subject token"); - } - - OAuth2Authorization.Token subjectToken = subjectAuthorization - .getToken(tokenExchangeAuthentication.getSubjectToken()); - if (!subjectToken.isActive()) { - // As per https://tools.ietf.org/html/rfc6749#section-5.2 - // invalid_grant: The provided authorization grant (e.g., authorization code, - // resource owner credentials) or refresh token is invalid, expired, revoked - // [...]. - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (!isValidTokenType(tokenExchangeAuthentication.getSubjectTokenType(), subjectToken)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - if (subjectAuthorization.getAttribute(Principal.class.getName()) == null) { - // As per https://datatracker.ietf.org/doc/html/rfc8693#section-1.1, - // we require a principal to be available via the subject_token for - // impersonation or delegation use cases. - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - // As per https://datatracker.ietf.org/doc/html/rfc8693#section-4.4, - // The may_act claim makes a statement that one party is authorized to - // become the actor and act on behalf of another party. - Map authorizedActorClaims = null; - if (subjectToken.getClaims() != null && subjectToken.getClaims().containsKey(MAY_ACT) - && subjectToken.getClaims().get(MAY_ACT) instanceof Map mayAct) { - authorizedActorClaims = (Map) mayAct; - } - - OAuth2Authorization actorAuthorization = null; - if (StringUtils.hasText(tokenExchangeAuthentication.getActorToken())) { - actorAuthorization = this.authorizationService.findByToken(tokenExchangeAuthentication.getActorToken(), - OAuth2TokenType.ACCESS_TOKEN); - if (actorAuthorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with actor token"); - } - - OAuth2Authorization.Token actorToken = actorAuthorization - .getToken(tokenExchangeAuthentication.getActorToken()); - if (!actorToken.isActive()) { - // As per https://tools.ietf.org/html/rfc6749#section-5.2 - // invalid_grant: The provided authorization grant (e.g., authorization - // code, - // resource owner credentials) or refresh token is invalid, expired, - // revoked [...]. - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - if (!isValidTokenType(tokenExchangeAuthentication.getActorTokenType(), actorToken)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - if (authorizedActorClaims != null) { - validateClaims(authorizedActorClaims, actorToken.getClaims(), OAuth2TokenClaimNames.ISS, - OAuth2TokenClaimNames.SUB); - } - } - else if (authorizedActorClaims != null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - Set authorizedScopes = Collections.emptySet(); - if (!CollectionUtils.isEmpty(tokenExchangeAuthentication.getScopes())) { - authorizedScopes = validateRequestedScopes(registeredClient, tokenExchangeAuthentication.getScopes()); - } - else if (!CollectionUtils.isEmpty(subjectAuthorization.getAuthorizedScopes())) { - authorizedScopes = validateRequestedScopes(registeredClient, subjectAuthorization.getAuthorizedScopes()); - } - - // Verify the DPoP Proof (if available) - Jwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(tokenExchangeAuthentication); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated token request parameters"); - } - - Authentication principal = getPrincipal(subjectAuthorization, actorAuthorization); - - // @formatter:off - DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .authorization(subjectAuthorization) - .principal(principal) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorizedScopes(authorizedScopes) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .authorizationGrant(tokenExchangeAuthentication); - // @formatter:on - if (dPoPProof != null) { - tokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProof); - } - - // ----- Access token ----- - OAuth2TokenContext tokenContext = tokenContextBuilder.build(); - OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); - if (generatedAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the access token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated access token"); - } - - // @formatter:off - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(subjectAuthorization.getPrincipalName()) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .authorizedScopes(authorizedScopes) - .attribute(Principal.class.getName(), principal); - // @formatter:on - - OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder, - generatedAccessToken, tokenContext); - - OAuth2Authorization authorization = authorizationBuilder.build(); - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization"); - } - - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.ISSUED_TOKEN_TYPE, - tokenExchangeAuthentication.getRequestedTokenType()); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated token request"); - } - - return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, null, - additionalParameters); - } - - private static boolean isValidTokenType(String tokenType, OAuth2Authorization.Token token) { - String tokenFormat = token.getMetadata(OAuth2TokenFormat.class.getName()); - return ACCESS_TOKEN_TYPE_VALUE.equals(tokenType) || JWT_TOKEN_TYPE_VALUE.equals(tokenType) - && OAuth2TokenFormat.SELF_CONTAINED.getValue().equals(tokenFormat); - } - - private static Set validateRequestedScopes(RegisteredClient registeredClient, Set requestedScopes) { - for (String requestedScope : requestedScopes) { - if (!registeredClient.getScopes().contains(requestedScope)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); - } - } - - return new LinkedHashSet<>(requestedScopes); - } - - private static void validateClaims(Map expectedClaims, Map actualClaims, - String... claimNames) { - if (actualClaims == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - - for (String claimName : claimNames) { - if (!Objects.equals(expectedClaims.get(claimName), actualClaims.get(claimName))) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - } - } - - private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization, - OAuth2Authorization actorAuthorization) { - Authentication subjectPrincipal = subjectAuthorization.getAttribute(Principal.class.getName()); - if (actorAuthorization == null) { - if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) { - return compositeAuthenticationToken.getSubject(); - } - return subjectPrincipal; - } - - // Capture claims for current actor's access token - OAuth2TokenExchangeActor currentActor = new OAuth2TokenExchangeActor( - actorAuthorization.getAccessToken().getClaims()); - List actorPrincipals = new LinkedList<>(); - actorPrincipals.add(currentActor); - - // Add chain of delegation for previous actor(s) if any - if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) { - subjectPrincipal = compositeAuthenticationToken.getSubject(); - actorPrincipals.addAll(compositeAuthenticationToken.getActors()); - } - - return new OAuth2TokenExchangeCompositeAuthenticationToken(subjectPrincipal, actorPrincipals); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2TokenExchangeAuthenticationToken.class.isAssignableFrom(authentication); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java deleted file mode 100644 index 24d6a9fc4..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for the OAuth 2.0 Token Exchange Grant. - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2AuthorizationGrantAuthenticationToken - * @see OAuth2TokenExchangeAuthenticationProvider - */ -public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { - - private final String requestedTokenType; - - private final String subjectToken; - - private final String subjectTokenType; - - private final String actorToken; - - private final String actorTokenType; - - private final Set resources; - - private final Set audiences; - - private final Set scopes; - - /** - * Constructs an {@code OAuth2TokenExchangeAuthenticationToken} using the provided - * parameters. - * @param requestedTokenType the requested token type - * @param subjectToken the subject token - * @param subjectTokenType the subject token type - * @param clientPrincipal the authenticated client principal - * @param actorToken the actor token - * @param actorTokenType the actor token type - * @param resources the requested resource URI(s) - * @param audiences the requested audience value(s) - * @param scopes the requested scope(s) - * @param additionalParameters the additional parameters - */ - public OAuth2TokenExchangeAuthenticationToken(String requestedTokenType, String subjectToken, - String subjectTokenType, Authentication clientPrincipal, @Nullable String actorToken, - @Nullable String actorTokenType, @Nullable Set resources, @Nullable Set audiences, - @Nullable Set scopes, @Nullable Map additionalParameters) { - super(AuthorizationGrantType.TOKEN_EXCHANGE, clientPrincipal, additionalParameters); - Assert.hasText(requestedTokenType, "requestedTokenType cannot be empty"); - Assert.hasText(subjectToken, "subjectToken cannot be empty"); - Assert.hasText(subjectTokenType, "subjectTokenType cannot be empty"); - this.requestedTokenType = requestedTokenType; - this.subjectToken = subjectToken; - this.subjectTokenType = subjectTokenType; - this.actorToken = actorToken; - this.actorTokenType = actorTokenType; - this.resources = Collections - .unmodifiableSet((resources != null) ? new LinkedHashSet<>(resources) : Collections.emptySet()); - this.audiences = Collections - .unmodifiableSet((audiences != null) ? new LinkedHashSet<>(audiences) : Collections.emptySet()); - this.scopes = Collections.unmodifiableSet((scopes != null) ? new HashSet<>(scopes) : Collections.emptySet()); - } - - /** - * Returns the requested token type. - * @return the requested token type - */ - public String getRequestedTokenType() { - return this.requestedTokenType; - } - - /** - * Returns the subject token. - * @return the subject token - */ - public String getSubjectToken() { - return this.subjectToken; - } - - /** - * Returns the subject token type. - * @return the subject token type - */ - public String getSubjectTokenType() { - return this.subjectTokenType; - } - - /** - * Returns the actor token. - * @return the actor token - */ - public String getActorToken() { - return this.actorToken; - } - - /** - * Returns the actor token type. - * @return the actor token type - */ - public String getActorTokenType() { - return this.actorTokenType; - } - - /** - * Returns the requested resource URI(s). - * @return the requested resource URI(s), or an empty {@code Set} if not available - */ - public Set getResources() { - return this.resources; - } - - /** - * Returns the requested audience value(s). - * @return the requested audience value(s), or an empty {@code Set} if not available - */ - public Set getAudiences() { - return this.audiences; - } - - /** - * Returns the requested scope(s). - * @return the requested scope(s), or an empty {@code Set} if not available - */ - public Set getScopes() { - return this.scopes; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java deleted file mode 100644 index 02d7f5b8e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for the OAuth 2.0 Token Exchange Grant to - * represent the principal in a composite token (e.g. the "delegation" use case). - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2TokenExchangeAuthenticationToken - */ -public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAuthenticationToken { - - private final Authentication subject; - - private final List actors; - - public OAuth2TokenExchangeCompositeAuthenticationToken(Authentication subject, - List actors) { - super((subject != null) ? subject.getAuthorities() : null); - Assert.notNull(subject, "subject cannot be null"); - Assert.notNull(actors, "actors cannot be null"); - this.subject = subject; - this.actors = Collections.unmodifiableList(new ArrayList<>(actors)); - setDetails(subject.getDetails()); - setAuthenticated(subject.isAuthenticated()); - } - - @Override - public Object getPrincipal() { - return this.subject.getPrincipal(); - } - - @Override - public Object getCredentials() { - return null; - } - - public Authentication getSubject() { - return this.subject; - } - - public List getActors() { - return this.actors; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof OAuth2TokenExchangeCompositeAuthenticationToken other)) { - return false; - } - return super.equals(obj) && Objects.equals(this.subject, other.subject) - && Objects.equals(this.actors, other.actors); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), this.subject, this.actors); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java deleted file mode 100644 index 2bb284277..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * An {@link AuthenticationProvider} implementation for OAuth 2.0 Token Introspection. - * - * @author Gerardo Roza - * @author Joe Grandja - * @since 0.1.1 - * @see OAuth2TokenIntrospectionAuthenticationToken - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see Section - * 2.1 Introspection Request - */ -public final class OAuth2TokenIntrospectionAuthenticationProvider implements AuthenticationProvider { - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor LIST_STRING_TYPE_DESCRIPTOR = TypeDescriptor.collection(List.class, - TypeDescriptor.valueOf(String.class)); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - /** - * Constructs an {@code OAuth2TokenIntrospectionAuthenticationProvider} using the - * provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public OAuth2TokenIntrospectionAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = (OAuth2TokenIntrospectionAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(tokenIntrospectionAuthentication); - - OAuth2Authorization authorization = this.authorizationService - .findByToken(tokenIntrospectionAuthentication.getToken(), null); - if (authorization == null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not authenticate token introspection request since token was not found"); - } - // Return the authentication request when token not found - return tokenIntrospectionAuthentication; - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with token"); - } - - OAuth2Authorization.Token authorizedToken = authorization - .getToken(tokenIntrospectionAuthentication.getToken()); - if (!authorizedToken.isActive()) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not introspect token since not active"); - } - return new OAuth2TokenIntrospectionAuthenticationToken(tokenIntrospectionAuthentication.getToken(), - clientPrincipal, OAuth2TokenIntrospection.builder().build()); - } - - RegisteredClient authorizedClient = this.registeredClientRepository - .findById(authorization.getRegisteredClientId()); - OAuth2TokenIntrospection tokenClaims = withActiveTokenClaims(authorizedToken, authorizedClient); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated token introspection request"); - } - - return new OAuth2TokenIntrospectionAuthenticationToken(authorizedToken.getToken().getTokenValue(), - clientPrincipal, tokenClaims); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2TokenIntrospectionAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static OAuth2TokenIntrospection withActiveTokenClaims( - OAuth2Authorization.Token authorizedToken, RegisteredClient authorizedClient) { - - OAuth2TokenIntrospection.Builder tokenClaims; - if (!CollectionUtils.isEmpty(authorizedToken.getClaims())) { - Map claims = convertClaimsIfNecessary(authorizedToken.getClaims()); - tokenClaims = OAuth2TokenIntrospection.withClaims(claims).active(true); - } - else { - tokenClaims = OAuth2TokenIntrospection.builder(true); - } - - tokenClaims.clientId(authorizedClient.getClientId()); - - // TODO Set "username" - - OAuth2Token token = authorizedToken.getToken(); - if (token.getIssuedAt() != null) { - tokenClaims.issuedAt(token.getIssuedAt()); - } - if (token.getExpiresAt() != null) { - tokenClaims.expiresAt(token.getExpiresAt()); - } - - if (OAuth2AccessToken.class.isAssignableFrom(token.getClass())) { - OAuth2AccessToken accessToken = (OAuth2AccessToken) token; - tokenClaims.tokenType(accessToken.getTokenType().getValue()); - } - - return tokenClaims.build(); - } - - private static Map convertClaimsIfNecessary(Map claims) { - Map convertedClaims = new HashMap<>(claims); - - Object value = claims.get(OAuth2TokenIntrospectionClaimNames.ISS); - if (value != null && !(value instanceof URL)) { - URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class); - if (convertedValue != null) { - convertedClaims.put(OAuth2TokenIntrospectionClaimNames.ISS, convertedValue); - } - } - - value = claims.get(OAuth2TokenIntrospectionClaimNames.SCOPE); - if (value != null && !(value instanceof List)) { - Object convertedValue = ClaimConversionService.getSharedInstance() - .convert(value, OBJECT_TYPE_DESCRIPTOR, LIST_STRING_TYPE_DESCRIPTOR); - if (convertedValue != null) { - convertedClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, convertedValue); - } - } - - value = claims.get(OAuth2TokenIntrospectionClaimNames.AUD); - if (value != null && !(value instanceof List)) { - Object convertedValue = ClaimConversionService.getSharedInstance() - .convert(value, OBJECT_TYPE_DESCRIPTOR, LIST_STRING_TYPE_DESCRIPTOR); - if (convertedValue != null) { - convertedClaims.put(OAuth2TokenIntrospectionClaimNames.AUD, convertedValue); - } - } - - return convertedClaims; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java deleted file mode 100644 index d2d73c272..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OAuth 2.0 Token Introspection. - * - * @author Gerardo Roza - * @author Joe Grandja - * @since 0.1.1 - * @see AbstractAuthenticationToken - * @see OAuth2TokenIntrospection - * @see OAuth2TokenIntrospectionAuthenticationProvider - */ -public class OAuth2TokenIntrospectionAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = 9003173975452760956L; - - private final String token; - - private final Authentication clientPrincipal; - - private final String tokenTypeHint; - - private final Map additionalParameters; - - private final OAuth2TokenIntrospection tokenClaims; - - /** - * Constructs an {@code OAuth2TokenIntrospectionAuthenticationToken} using the - * provided parameters. - * @param token the token - * @param clientPrincipal the authenticated client principal - * @param tokenTypeHint the token type hint - * @param additionalParameters the additional parameters - */ - public OAuth2TokenIntrospectionAuthenticationToken(String token, Authentication clientPrincipal, - @Nullable String tokenTypeHint, @Nullable Map additionalParameters) { - super(Collections.emptyList()); - Assert.hasText(token, "token cannot be empty"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - this.token = token; - this.clientPrincipal = clientPrincipal; - this.tokenTypeHint = tokenTypeHint; - this.additionalParameters = Collections.unmodifiableMap( - (additionalParameters != null) ? new HashMap<>(additionalParameters) : Collections.emptyMap()); - this.tokenClaims = OAuth2TokenIntrospection.builder().build(); - } - - /** - * Constructs an {@code OAuth2TokenIntrospectionAuthenticationToken} using the - * provided parameters. - * @param token the token - * @param clientPrincipal the authenticated client principal - * @param tokenClaims the token claims - */ - public OAuth2TokenIntrospectionAuthenticationToken(String token, Authentication clientPrincipal, - OAuth2TokenIntrospection tokenClaims) { - super(Collections.emptyList()); - Assert.hasText(token, "token cannot be empty"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - Assert.notNull(tokenClaims, "tokenClaims cannot be null"); - this.token = token; - this.clientPrincipal = clientPrincipal; - this.tokenTypeHint = null; - this.additionalParameters = Collections.emptyMap(); - this.tokenClaims = tokenClaims; - // Indicates that the request was authenticated, even though the token might not - // be active - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.clientPrincipal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the token. - * @return the token - */ - public String getToken() { - return this.token; - } - - /** - * Returns the token type hint. - * @return the token type hint - */ - @Nullable - public String getTokenTypeHint() { - return this.tokenTypeHint; - } - - /** - * Returns the additional parameters. - * @return the additional parameters - */ - public Map getAdditionalParameters() { - return this.additionalParameters; - } - - /** - * Returns the token claims. - * @return the {@link OAuth2TokenIntrospection} - */ - public OAuth2TokenIntrospection getTokenClaims() { - return this.tokenClaims; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java deleted file mode 100644 index bd284b8b1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for OAuth 2.0 Token Revocation. - * - * @author Vivek Babu - * @author Joe Grandja - * @since 0.0.3 - * @see OAuth2TokenRevocationAuthenticationToken - * @see OAuth2AuthorizationService - * @see Section - * 2.1 Revocation Request - */ -public final class OAuth2TokenRevocationAuthenticationProvider implements AuthenticationProvider { - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - /** - * Constructs an {@code OAuth2TokenRevocationAuthenticationProvider} using the - * provided parameters. - * @param authorizationService the authorization service - */ - public OAuth2TokenRevocationAuthenticationProvider(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.authorizationService = authorizationService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = (OAuth2TokenRevocationAuthenticationToken) authentication; - - OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils - .getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication); - RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); - - OAuth2Authorization authorization = this.authorizationService - .findByToken(tokenRevocationAuthentication.getToken(), null); - if (authorization == null) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Did not authenticate token revocation request since token was not found"); - } - // Return the authentication request when token not found - return tokenRevocationAuthentication; - } - - if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); - } - - OAuth2Authorization.Token token = authorization.getToken(tokenRevocationAuthentication.getToken()); - authorization = OAuth2Authorization.from(authorization).invalidate(token.getToken()).build(); - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization with revoked token"); - // This log is kept separate for consistency with other providers - this.logger.trace("Authenticated token revocation request"); - } - - return new OAuth2TokenRevocationAuthenticationToken(token.getToken(), clientPrincipal); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2TokenRevocationAuthenticationToken.class.isAssignableFrom(authentication); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java deleted file mode 100644 index c4a1cb58f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.io.Serial; -import java.util.Collections; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OAuth 2.0 Token Revocation. - * - * @author Vivek Babu - * @author Joe Grandja - * @since 0.0.3 - * @see AbstractAuthenticationToken - * @see OAuth2TokenRevocationAuthenticationProvider - */ -public class OAuth2TokenRevocationAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -880609099230203249L; - - private final String token; - - private final Authentication clientPrincipal; - - private final String tokenTypeHint; - - /** - * Constructs an {@code OAuth2TokenRevocationAuthenticationToken} using the provided - * parameters. - * @param token the token - * @param clientPrincipal the authenticated client principal - * @param tokenTypeHint the token type hint - */ - public OAuth2TokenRevocationAuthenticationToken(String token, Authentication clientPrincipal, - @Nullable String tokenTypeHint) { - super(Collections.emptyList()); - Assert.hasText(token, "token cannot be empty"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - this.token = token; - this.clientPrincipal = clientPrincipal; - this.tokenTypeHint = tokenTypeHint; - } - - /** - * Constructs an {@code OAuth2TokenRevocationAuthenticationToken} using the provided - * parameters. - * @param revokedToken the revoked token - * @param clientPrincipal the authenticated client principal - */ - public OAuth2TokenRevocationAuthenticationToken(OAuth2Token revokedToken, Authentication clientPrincipal) { - super(Collections.emptyList()); - Assert.notNull(revokedToken, "revokedToken cannot be null"); - Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); - this.token = revokedToken.getTokenValue(); - this.clientPrincipal = clientPrincipal; - this.tokenTypeHint = null; - setAuthenticated(true); // Indicates that the token was authenticated and revoked - } - - @Override - public Object getPrincipal() { - return this.clientPrincipal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the token. - * @return the token - */ - public String getToken() { - return this.token; - } - - /** - * Returns the token type hint. - * @return the token type hint - */ - @Nullable - public String getTokenTypeHint() { - return this.tokenTypeHint; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java deleted file mode 100644 index 2af3b9d2b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -/** - * The values defined for the "prompt" parameter for the OpenID Connect 1.0 Authentication - * Request. - * - * @author Joe Grandja - * @since 1.5 - */ -final class OidcPrompt { - - static final String NONE = "none"; - - static final String LOGIN = "login"; - - static final String CONSENT = "consent"; - - static final String SELECT_ACCOUNT = "select_account"; - - private OidcPrompt() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java deleted file mode 100644 index 075cf72cd..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation used for OAuth 2.0 Public Client - * Authentication, which authenticates the {@link PkceParameterNames#CODE_VERIFIER - * code_verifier} parameter. - * - * @author Joe Grandja - * @since 0.2.3 - * @see AuthenticationProvider - * @see OAuth2ClientAuthenticationToken - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - */ -public final class PublicClientAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final CodeVerifierAuthenticator codeVerifierAuthenticator; - - /** - * Constructs a {@code PublicClientAuthenticationProvider} using the provided - * parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public PublicClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService); - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; - - if (!ClientAuthenticationMethod.NONE.equals(clientAuthentication.getClientAuthenticationMethod())) { - return null; - } - - String clientId = clientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getClientAuthenticationMethods() - .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client authentication parameters"); - } - - // Validate the "code_verifier" parameter for the public client - this.codeVerifierAuthenticator.authenticateRequired(clientAuthentication, registeredClient); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated public client"); - } - - return new OAuth2ClientAuthenticationToken(registeredClient, - clientAuthentication.getClientAuthenticationMethod(), null); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static void throwInvalidClient(String parameterName) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java deleted file mode 100644 index 799516aec..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.cert.X509Certificate; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client - * Authentication, which authenticates the client {@code X509Certificate} received when - * the {@code tls_client_auth} or {@code self_signed_tls_client_auth} authentication - * method is used. - * - * @author Joe Grandja - * @since 1.3 - * @see AuthenticationProvider - * @see OAuth2ClientAuthenticationToken - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - */ -public final class X509ClientCertificateAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final CodeVerifierAuthenticator codeVerifierAuthenticator; - - private final Consumer selfSignedCertificateVerifier = new X509SelfSignedCertificateVerifier(); - - private Consumer certificateVerifier = this::verifyX509Certificate; - - /** - * Constructs a {@code X509ClientCertificateAuthenticationProvider} using the provided - * parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public X509ClientCertificateAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService); - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; - - if (!ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) - && !ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH - .equals(clientAuthentication.getClientAuthenticationMethod())) { - return null; - } - - String clientId = clientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - if (!registeredClient.getClientAuthenticationMethods() - .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); - } - - if (!(clientAuthentication.getCredentials() instanceof X509Certificate[])) { - throwInvalidClient("credentials"); - } - - OAuth2ClientAuthenticationContext authenticationContext = OAuth2ClientAuthenticationContext - .with(clientAuthentication) - .registeredClient(registeredClient) - .build(); - this.certificateVerifier.accept(authenticationContext); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client authentication parameters"); - } - - // Validate the "code_verifier" parameter for the confidential client, if - // available - this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated client X509Certificate"); - } - - return new OAuth2ClientAuthenticationToken(registeredClient, - clientAuthentication.getClientAuthenticationMethod(), clientAuthentication.getCredentials()); - } - - @Override - public boolean supports(Class authentication) { - return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2ClientAuthenticationContext} and is responsible for verifying the - * client {@code X509Certificate} associated in the - * {@link OAuth2ClientAuthenticationToken}. The default implementation for the - * {@code tls_client_auth} authentication method verifies the - * {@link ClientSettings#getX509CertificateSubjectDN() expected subject distinguished - * name}. - * - *

- * NOTE: If verification fails, an {@link OAuth2AuthenticationException} MUST - * be thrown. - * @param certificateVerifier the {@code Consumer} providing access to the - * {@link OAuth2ClientAuthenticationContext} and is responsible for verifying the - * client {@code X509Certificate} - */ - public void setCertificateVerifier(Consumer certificateVerifier) { - Assert.notNull(certificateVerifier, "certificateVerifier cannot be null"); - this.certificateVerifier = certificateVerifier; - } - - private void verifyX509Certificate(OAuth2ClientAuthenticationContext clientAuthenticationContext) { - OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication(); - if (ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH - .equals(clientAuthentication.getClientAuthenticationMethod())) { - this.selfSignedCertificateVerifier.accept(clientAuthenticationContext); - } - else { - verifyX509CertificateSubjectDN(clientAuthenticationContext); - } - } - - private void verifyX509CertificateSubjectDN(OAuth2ClientAuthenticationContext clientAuthenticationContext) { - OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication(); - RegisteredClient registeredClient = clientAuthenticationContext.getRegisteredClient(); - X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials(); - X509Certificate clientCertificate = clientCertificateChain[0]; - String expectedSubjectDN = registeredClient.getClientSettings().getX509CertificateSubjectDN(); - if (!StringUtils.hasText(expectedSubjectDN) - || !clientCertificate.getSubjectX500Principal().getName().equals(expectedSubjectDN)) { - throwInvalidClient("x509_certificate_subject_dn"); - } - } - - private static void throwInvalidClient(String parameterName) { - throwInvalidClient(parameterName, null); - } - - private static void throwInvalidClient(String parameterName, Throwable cause) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error, error.toString(), cause); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java deleted file mode 100644 index 964c75922..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.net.URI; -import java.net.URISyntaxException; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.text.ParseException; -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import javax.security.auth.x500.X500Principal; - -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.JWKMatcher; -import com.nimbusds.jose.jwk.JWKSet; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -/** - * The default {@code X509Certificate} verifier for the - * {@code self_signed_tls_client_auth} authentication method. - * - * @author Joe Grandja - * @since 1.3 - * @see X509ClientCertificateAuthenticationProvider#setCertificateVerifier(Consumer) - */ -final class X509SelfSignedCertificateVerifier implements Consumer { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - - private static final JWKMatcher HAS_X509_CERT_CHAIN_MATCHER = new JWKMatcher.Builder().hasX509CertChain(true) - .build(); - - private final Function jwkSetSupplier = new JwkSetSupplier(); - - @Override - public void accept(OAuth2ClientAuthenticationContext clientAuthenticationContext) { - OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication(); - RegisteredClient registeredClient = clientAuthenticationContext.getRegisteredClient(); - X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials(); - X509Certificate clientCertificate = clientCertificateChain[0]; - - X500Principal issuer = clientCertificate.getIssuerX500Principal(); - X500Principal subject = clientCertificate.getSubjectX500Principal(); - if (issuer == null || !issuer.equals(subject)) { - throwInvalidClient("x509_certificate_issuer"); - } - - JWKSet jwkSet = this.jwkSetSupplier.apply(registeredClient); - - boolean publicKeyMatches = false; - for (JWK jwk : jwkSet.filter(HAS_X509_CERT_CHAIN_MATCHER).getKeys()) { - X509Certificate x509Certificate = jwk.getParsedX509CertChain().get(0); - PublicKey publicKey = x509Certificate.getPublicKey(); - if (Arrays.equals(clientCertificate.getPublicKey().getEncoded(), publicKey.getEncoded())) { - publicKeyMatches = true; - break; - } - } - - if (!publicKeyMatches) { - throwInvalidClient("x509_certificate"); - } - } - - private static void throwInvalidClient(String parameterName) { - throwInvalidClient(parameterName, null); - } - - private static void throwInvalidClient(String parameterName, Throwable cause) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error, error.toString(), cause); - } - - private static final class JwkSetSupplier implements Function { - - private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json"); - - private final RestOperations restOperations; - - private final Map> jwkSets = new ConcurrentHashMap<>(); - - private JwkSetSupplier() { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - requestFactory.setConnectTimeout(15_000); - requestFactory.setReadTimeout(15_000); - this.restOperations = new RestTemplate(requestFactory); - } - - @Override - public JWKSet apply(RegisteredClient registeredClient) { - Supplier jwkSetSupplier = this.jwkSets.computeIfAbsent(registeredClient.getId(), (key) -> { - if (!StringUtils.hasText(registeredClient.getClientSettings().getJwkSetUrl())) { - throwInvalidClient("client_jwk_set_url"); - } - return new JwkSetHolder(registeredClient.getClientSettings().getJwkSetUrl()); - }); - return jwkSetSupplier.get(); - } - - private JWKSet retrieve(String jwkSetUrl) { - URI jwkSetUri = null; - try { - jwkSetUri = new URI(jwkSetUrl); - } - catch (URISyntaxException ex) { - throwInvalidClient("jwk_set_uri", ex); - } - - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON)); - RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, jwkSetUri); - ResponseEntity response = null; - try { - response = this.restOperations.exchange(request, String.class); - } - catch (Exception ex) { - throwInvalidClient("jwk_set_response_error", ex); - } - if (response.getStatusCode().value() != 200) { - throwInvalidClient("jwk_set_response_status"); - } - - JWKSet jwkSet = null; - try { - jwkSet = JWKSet.parse(response.getBody()); - } - catch (ParseException ex) { - throwInvalidClient("jwk_set_response_body", ex); - } - - return jwkSet; - } - - private final class JwkSetHolder implements Supplier { - - private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); - - private final Clock clock = Clock.systemUTC(); - - private final String jwkSetUrl; - - private JWKSet jwkSet; - - private Instant lastUpdatedAt; - - private JwkSetHolder(String jwkSetUrl) { - this.jwkSetUrl = jwkSetUrl; - } - - @Override - public JWKSet get() { - this.rwLock.readLock().lock(); - if (shouldRefresh()) { - this.rwLock.readLock().unlock(); - this.rwLock.writeLock().lock(); - try { - if (shouldRefresh()) { - this.jwkSet = retrieve(this.jwkSetUrl); - this.lastUpdatedAt = Instant.now(); - } - this.rwLock.readLock().lock(); - } - finally { - this.rwLock.writeLock().unlock(); - } - } - - try { - return this.jwkSet; - } - finally { - this.rwLock.readLock().unlock(); - } - } - - private boolean shouldRefresh() { - // Refresh every 5 minutes - return (this.jwkSet == null - || this.clock.instant().isAfter(this.lastUpdatedAt.plus(5, ChronoUnit.MINUTES))); - } - - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepository.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepository.java deleted file mode 100644 index 69d91745e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepository.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.client; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A {@link RegisteredClientRepository} that stores {@link RegisteredClient}(s) in-memory. - * - *

- * NOTE: This implementation is recommended ONLY to be used during - * development/testing. - * - * @author Anoop Garlapati - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.0.1 - * @see RegisteredClientRepository - * @see RegisteredClient - */ -public final class InMemoryRegisteredClientRepository implements RegisteredClientRepository { - - private final Map idRegistrationMap; - - private final Map clientIdRegistrationMap; - - /** - * Constructs an {@code InMemoryRegisteredClientRepository} using the provided - * parameters. - * @param registrations the client registration(s) - */ - public InMemoryRegisteredClientRepository(RegisteredClient... registrations) { - this(Arrays.asList(registrations)); - } - - /** - * Constructs an {@code InMemoryRegisteredClientRepository} using the provided - * parameters. - * @param registrations the client registration(s) - */ - public InMemoryRegisteredClientRepository(List registrations) { - Assert.notEmpty(registrations, "registrations cannot be empty"); - ConcurrentHashMap idRegistrationMapResult = new ConcurrentHashMap<>(); - ConcurrentHashMap clientIdRegistrationMapResult = new ConcurrentHashMap<>(); - for (RegisteredClient registration : registrations) { - Assert.notNull(registration, "registration cannot be null"); - assertUniqueIdentifiers(registration, idRegistrationMapResult); - idRegistrationMapResult.put(registration.getId(), registration); - clientIdRegistrationMapResult.put(registration.getClientId(), registration); - } - this.idRegistrationMap = idRegistrationMapResult; - this.clientIdRegistrationMap = clientIdRegistrationMapResult; - } - - @Override - public void save(RegisteredClient registeredClient) { - Assert.notNull(registeredClient, "registeredClient cannot be null"); - if (!this.idRegistrationMap.containsKey(registeredClient.getId())) { - assertUniqueIdentifiers(registeredClient, this.idRegistrationMap); - } - this.idRegistrationMap.put(registeredClient.getId(), registeredClient); - this.clientIdRegistrationMap.put(registeredClient.getClientId(), registeredClient); - } - - @Nullable - @Override - public RegisteredClient findById(String id) { - Assert.hasText(id, "id cannot be empty"); - return this.idRegistrationMap.get(id); - } - - @Nullable - @Override - public RegisteredClient findByClientId(String clientId) { - Assert.hasText(clientId, "clientId cannot be empty"); - return this.clientIdRegistrationMap.get(clientId); - } - - private void assertUniqueIdentifiers(RegisteredClient registeredClient, - Map registrations) { - registrations.values().forEach((registration) -> { - if (registeredClient.getId().equals(registration.getId())) { - throw new IllegalArgumentException("Registered client must be unique. " + "Found duplicate identifier: " - + registeredClient.getId()); - } - if (registeredClient.getClientId().equals(registration.getClientId())) { - throw new IllegalArgumentException("Registered client must be unique. " - + "Found duplicate client identifier: " + registeredClient.getClientId()); - } - if (StringUtils.hasText(registeredClient.getClientSecret()) - && registeredClient.getClientSecret().equals(registration.getClientSecret())) { - throw new IllegalArgumentException("Registered client must be unique. " - + "Found duplicate client secret for identifier: " + registeredClient.getId()); - } - }); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java deleted file mode 100644 index 112c238f0..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.client; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.io.ClassPathResource; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A JDBC implementation of a {@link RegisteredClientRepository} that uses a - * {@link JdbcOperations} for {@link RegisteredClient} persistence. - * - *

- * IMPORTANT: This {@code RegisteredClientRepository} depends on the table - * definition described in - * "classpath:org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql" - * and therefore MUST be defined in the database schema. - * - *

- * NOTE: This {@code RegisteredClientRepository} is a simplified JDBC - * implementation that MAY be used in a production environment. However, it does have - * limitations as it likely won't perform well in an environment requiring high - * throughput. The expectation is that the consuming application will provide their own - * implementation of {@code RegisteredClientRepository} that meets the performance - * requirements for its deployment environment. - * - * @author Rafal Lewczuk - * @author Joe Grandja - * @author Ovidiu Popa - * @author Josh Long - * @since 0.1.2 - * @see RegisteredClientRepository - * @see RegisteredClient - * @see JdbcOperations - * @see RowMapper - */ -@ImportRuntimeHints(JdbcRegisteredClientRepository.JdbcRegisteredClientRepositoryRuntimeHintsRegistrar.class) -public class JdbcRegisteredClientRepository implements RegisteredClientRepository { - - // @formatter:off - private static final String COLUMN_NAMES = "id, " - + "client_id, " - + "client_id_issued_at, " - + "client_secret, " - + "client_secret_expires_at, " - + "client_name, " - + "client_authentication_methods, " - + "authorization_grant_types, " - + "redirect_uris, " - + "post_logout_redirect_uris, " - + "scopes, " - + "client_settings," - + "token_settings"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2_registered_client"; - - private static final String PK_FILTER = "id = ?"; - - private static final String LOAD_REGISTERED_CLIENT_SQL = "SELECT " + COLUMN_NAMES + " FROM " + TABLE_NAME - + " WHERE "; - - // @formatter:off - private static final String INSERT_REGISTERED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME - + "(" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - // @formatter:on - - // @formatter:off - private static final String UPDATE_REGISTERED_CLIENT_SQL = "UPDATE " + TABLE_NAME - + " SET client_secret = ?, client_secret_expires_at = ?, client_name = ?, client_authentication_methods = ?," - + " authorization_grant_types = ?, redirect_uris = ?, post_logout_redirect_uris = ?, scopes = ?," - + " client_settings = ?, token_settings = ?" - + " WHERE " + PK_FILTER; - // @formatter:on - - private static final String COUNT_REGISTERED_CLIENT_SQL = "SELECT COUNT(*) FROM " + TABLE_NAME + " WHERE "; - - private final JdbcOperations jdbcOperations; - - private RowMapper registeredClientRowMapper; - - private Function> registeredClientParametersMapper; - - /** - * Constructs a {@code JdbcRegisteredClientRepository} using the provided parameters. - * @param jdbcOperations the JDBC operations - */ - public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) { - Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); - this.jdbcOperations = jdbcOperations; - this.registeredClientRowMapper = new RegisteredClientRowMapper(); - this.registeredClientParametersMapper = new RegisteredClientParametersMapper(); - } - - @Override - public void save(RegisteredClient registeredClient) { - Assert.notNull(registeredClient, "registeredClient cannot be null"); - RegisteredClient existingRegisteredClient = findBy(PK_FILTER, registeredClient.getId()); - if (existingRegisteredClient != null) { - updateRegisteredClient(registeredClient); - } - else { - insertRegisteredClient(registeredClient); - } - } - - private void updateRegisteredClient(RegisteredClient registeredClient) { - List parameters = new ArrayList<>( - this.registeredClientParametersMapper.apply(registeredClient)); - SqlParameterValue id = parameters.remove(0); - parameters.remove(0); // remove client_id - parameters.remove(0); // remove client_id_issued_at - parameters.add(id); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - this.jdbcOperations.update(UPDATE_REGISTERED_CLIENT_SQL, pss); - } - - private void insertRegisteredClient(RegisteredClient registeredClient) { - assertUniqueIdentifiers(registeredClient); - List parameters = this.registeredClientParametersMapper.apply(registeredClient); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - this.jdbcOperations.update(INSERT_REGISTERED_CLIENT_SQL, pss); - } - - private void assertUniqueIdentifiers(RegisteredClient registeredClient) { - Integer count = this.jdbcOperations.queryForObject(COUNT_REGISTERED_CLIENT_SQL + "client_id = ?", Integer.class, - registeredClient.getClientId()); - if (count != null && count > 0) { - throw new IllegalArgumentException("Registered client must be unique. " - + "Found duplicate client identifier: " + registeredClient.getClientId()); - } - if (StringUtils.hasText(registeredClient.getClientSecret())) { - count = this.jdbcOperations.queryForObject(COUNT_REGISTERED_CLIENT_SQL + "client_secret = ?", Integer.class, - registeredClient.getClientSecret()); - if (count != null && count > 0) { - throw new IllegalArgumentException("Registered client must be unique. " - + "Found duplicate client secret for identifier: " + registeredClient.getId()); - } - } - } - - @Override - public RegisteredClient findById(String id) { - Assert.hasText(id, "id cannot be empty"); - return findBy("id = ?", id); - } - - @Override - public RegisteredClient findByClientId(String clientId) { - Assert.hasText(clientId, "clientId cannot be empty"); - return findBy("client_id = ?", clientId); - } - - private RegisteredClient findBy(String filter, Object... args) { - List result = this.jdbcOperations.query(LOAD_REGISTERED_CLIENT_SQL + filter, - this.registeredClientRowMapper, args); - return !result.isEmpty() ? result.get(0) : null; - } - - /** - * Sets the {@link RowMapper} used for mapping the current row in - * {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is - * {@link RegisteredClientRowMapper}. - * @param registeredClientRowMapper the {@link RowMapper} used for mapping the current - * row in {@code ResultSet} to {@link RegisteredClient} - */ - public final void setRegisteredClientRowMapper(RowMapper registeredClientRowMapper) { - Assert.notNull(registeredClientRowMapper, "registeredClientRowMapper cannot be null"); - this.registeredClientRowMapper = registeredClientRowMapper; - } - - /** - * Sets the {@code Function} used for mapping {@link RegisteredClient} to a - * {@code List} of {@link SqlParameterValue}. The default is - * {@link RegisteredClientParametersMapper}. - * @param registeredClientParametersMapper the {@code Function} used for mapping - * {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue} - */ - public final void setRegisteredClientParametersMapper( - Function> registeredClientParametersMapper) { - Assert.notNull(registeredClientParametersMapper, "registeredClientParametersMapper cannot be null"); - this.registeredClientParametersMapper = registeredClientParametersMapper; - } - - protected final JdbcOperations getJdbcOperations() { - return this.jdbcOperations; - } - - protected final RowMapper getRegisteredClientRowMapper() { - return this.registeredClientRowMapper; - } - - protected final Function> getRegisteredClientParametersMapper() { - return this.registeredClientParametersMapper; - } - - /** - * The default {@link RowMapper} that maps the current row in - * {@code java.sql.ResultSet} to {@link RegisteredClient}. - */ - public static class RegisteredClientRowMapper implements RowMapper { - - private ObjectMapper objectMapper = new ObjectMapper(); - - public RegisteredClientRowMapper() { - ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Override - public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException { - Timestamp clientIdIssuedAt = rs.getTimestamp("client_id_issued_at"); - Timestamp clientSecretExpiresAt = rs.getTimestamp("client_secret_expires_at"); - Set clientAuthenticationMethods = StringUtils - .commaDelimitedListToSet(rs.getString("client_authentication_methods")); - Set authorizationGrantTypes = StringUtils - .commaDelimitedListToSet(rs.getString("authorization_grant_types")); - Set redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirect_uris")); - Set postLogoutRedirectUris = StringUtils - .commaDelimitedListToSet(rs.getString("post_logout_redirect_uris")); - Set clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes")); - - // @formatter:off - RegisteredClient.Builder builder = RegisteredClient.withId(rs.getString("id")) - .clientId(rs.getString("client_id")) - .clientIdIssuedAt((clientIdIssuedAt != null) ? clientIdIssuedAt.toInstant() : null) - .clientSecret(rs.getString("client_secret")) - .clientSecretExpiresAt((clientSecretExpiresAt != null) ? clientSecretExpiresAt.toInstant() : null) - .clientName(rs.getString("client_name")) - .clientAuthenticationMethods((authenticationMethods) -> - clientAuthenticationMethods.forEach((authenticationMethod) -> - authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod)))) - .authorizationGrantTypes((grantTypes) -> - authorizationGrantTypes.forEach((grantType) -> - grantTypes.add(resolveAuthorizationGrantType(grantType)))) - .redirectUris((uris) -> uris.addAll(redirectUris)) - .postLogoutRedirectUris((uris) -> uris.addAll(postLogoutRedirectUris)) - .scopes((scopes) -> scopes.addAll(clientScopes)); - // @formatter:on - - Map clientSettingsMap = parseMap(rs.getString("client_settings")); - builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build()); - - Map tokenSettingsMap = parseMap(rs.getString("token_settings")); - TokenSettings.Builder tokenSettingsBuilder = TokenSettings.withSettings(tokenSettingsMap); - if (!tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) { - tokenSettingsBuilder.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED); - } - builder.tokenSettings(tokenSettingsBuilder.build()); - - return builder.build(); - } - - public final void setObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "objectMapper cannot be null"); - this.objectMapper = objectMapper; - } - - protected final ObjectMapper getObjectMapper() { - return this.objectMapper; - } - - private Map parseMap(String data) { - try { - return this.objectMapper.readValue(data, new TypeReference<>() { - }); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) { - if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.AUTHORIZATION_CODE; - } - else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.CLIENT_CREDENTIALS; - } - else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.REFRESH_TOKEN; - } - // Custom authorization grant type - return new AuthorizationGrantType(authorizationGrantType); - } - - private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) { - if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.CLIENT_SECRET_BASIC; - } - else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.CLIENT_SECRET_POST; - } - else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.NONE; - } - // Custom client authentication method - return new ClientAuthenticationMethod(clientAuthenticationMethod); - } - - } - - /** - * The default {@code Function} that maps {@link RegisteredClient} to a {@code List} - * of {@link SqlParameterValue}. - */ - public static class RegisteredClientParametersMapper - implements Function> { - - private ObjectMapper objectMapper = new ObjectMapper(); - - public RegisteredClientParametersMapper() { - ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Override - public List apply(RegisteredClient registeredClient) { - Timestamp clientIdIssuedAt = (registeredClient.getClientIdIssuedAt() != null) - ? Timestamp.from(registeredClient.getClientIdIssuedAt()) : Timestamp.from(Instant.now()); - - Timestamp clientSecretExpiresAt = (registeredClient.getClientSecretExpiresAt() != null) - ? Timestamp.from(registeredClient.getClientSecretExpiresAt()) : null; - - List clientAuthenticationMethods = new ArrayList<>( - registeredClient.getClientAuthenticationMethods().size()); - registeredClient.getClientAuthenticationMethods() - .forEach((clientAuthenticationMethod) -> clientAuthenticationMethods - .add(clientAuthenticationMethod.getValue())); - - List authorizationGrantTypes = new ArrayList<>( - registeredClient.getAuthorizationGrantTypes().size()); - registeredClient.getAuthorizationGrantTypes() - .forEach((authorizationGrantType) -> authorizationGrantTypes.add(authorizationGrantType.getValue())); - - return Arrays.asList(new SqlParameterValue(Types.VARCHAR, registeredClient.getId()), - new SqlParameterValue(Types.VARCHAR, registeredClient.getClientId()), - new SqlParameterValue(Types.TIMESTAMP, clientIdIssuedAt), - new SqlParameterValue(Types.VARCHAR, registeredClient.getClientSecret()), - new SqlParameterValue(Types.TIMESTAMP, clientSecretExpiresAt), - new SqlParameterValue(Types.VARCHAR, registeredClient.getClientName()), - new SqlParameterValue(Types.VARCHAR, - StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods)), - new SqlParameterValue(Types.VARCHAR, - StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes)), - new SqlParameterValue(Types.VARCHAR, - StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris())), - new SqlParameterValue(Types.VARCHAR, - StringUtils.collectionToCommaDelimitedString(registeredClient.getPostLogoutRedirectUris())), - new SqlParameterValue(Types.VARCHAR, - StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes())), - new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getClientSettings().getSettings())), - new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().getSettings()))); - } - - public final void setObjectMapper(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "objectMapper cannot be null"); - this.objectMapper = objectMapper; - } - - protected final ObjectMapper getObjectMapper() { - return this.objectMapper; - } - - private String writeMap(Map data) { - try { - return this.objectMapper.writeValueAsString(data); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - } - - static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - hints.resources() - .registerResource(new ClassPathResource( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java deleted file mode 100644 index f4a4a050c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.client; - -import java.io.Serial; -import java.io.Serializable; -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Instant; -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * A representation of a client registration with an OAuth 2.0 Authorization Server. - * - * @author Joe Grandja - * @author Anoop Garlapati - * @since 0.0.1 - * @see Section 2 - * Client Registration - */ -public class RegisteredClient implements Serializable { - - @Serial - private static final long serialVersionUID = -717282636175335081L; - - private String id; - - private String clientId; - - private Instant clientIdIssuedAt; - - private String clientSecret; - - private Instant clientSecretExpiresAt; - - private String clientName; - - private Set clientAuthenticationMethods; - - private Set authorizationGrantTypes; - - private Set redirectUris; - - private Set postLogoutRedirectUris; - - private Set scopes; - - private ClientSettings clientSettings; - - private TokenSettings tokenSettings; - - protected RegisteredClient() { - } - - /** - * Returns the identifier for the registration. - * @return the identifier for the registration - */ - public String getId() { - return this.id; - } - - /** - * Returns the client identifier. - * @return the client identifier - */ - public String getClientId() { - return this.clientId; - } - - /** - * Returns the time at which the client identifier was issued. - * @return the time at which the client identifier was issued - */ - @Nullable - public Instant getClientIdIssuedAt() { - return this.clientIdIssuedAt; - } - - /** - * Returns the client secret or {@code null} if not available. - * @return the client secret or {@code null} if not available - */ - @Nullable - public String getClientSecret() { - return this.clientSecret; - } - - /** - * Returns the time at which the client secret expires or {@code null} if it does not - * expire. - * @return the time at which the client secret expires or {@code null} if it does not - * expire - */ - @Nullable - public Instant getClientSecretExpiresAt() { - return this.clientSecretExpiresAt; - } - - /** - * Returns the client name. - * @return the client name - */ - public String getClientName() { - return this.clientName; - } - - /** - * Returns the {@link ClientAuthenticationMethod authentication method(s)} that the - * client may use. - * @return the {@code Set} of {@link ClientAuthenticationMethod authentication - * method(s)} - */ - public Set getClientAuthenticationMethods() { - return this.clientAuthenticationMethods; - } - - /** - * Returns the {@link AuthorizationGrantType authorization grant type(s)} that the - * client may use. - * @return the {@code Set} of {@link AuthorizationGrantType authorization grant - * type(s)} - */ - public Set getAuthorizationGrantTypes() { - return this.authorizationGrantTypes; - } - - /** - * Returns the redirect URI(s) that the client may use in redirect-based flows. - * @return the {@code Set} of redirect URI(s) - */ - public Set getRedirectUris() { - return this.redirectUris; - } - - /** - * Returns the post logout redirect URI(s) that the client may use for logout. The - * {@code post_logout_redirect_uri} parameter is used by the client when requesting - * that the End-User's User Agent be redirected to after a logout has been performed. - * @return the {@code Set} of post logout redirect URI(s) - * @since 1.1 - */ - public Set getPostLogoutRedirectUris() { - return this.postLogoutRedirectUris; - } - - /** - * Returns the scope(s) that the client may use. - * @return the {@code Set} of scope(s) - */ - public Set getScopes() { - return this.scopes; - } - - /** - * Returns the {@link ClientSettings client configuration settings}. - * @return the {@link ClientSettings} - */ - public ClientSettings getClientSettings() { - return this.clientSettings; - } - - /** - * Returns the {@link TokenSettings token configuration settings}. - * @return the {@link TokenSettings} - */ - public TokenSettings getTokenSettings() { - return this.tokenSettings; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - RegisteredClient that = (RegisteredClient) obj; - return Objects.equals(this.id, that.id) && Objects.equals(this.clientId, that.clientId) - && Objects.equals(this.clientIdIssuedAt, that.clientIdIssuedAt) - && Objects.equals(this.clientSecret, that.clientSecret) - && Objects.equals(this.clientSecretExpiresAt, that.clientSecretExpiresAt) - && Objects.equals(this.clientName, that.clientName) - && Objects.equals(this.clientAuthenticationMethods, that.clientAuthenticationMethods) - && Objects.equals(this.authorizationGrantTypes, that.authorizationGrantTypes) - && Objects.equals(this.redirectUris, that.redirectUris) - && Objects.equals(this.postLogoutRedirectUris, that.postLogoutRedirectUris) - && Objects.equals(this.scopes, that.scopes) && Objects.equals(this.clientSettings, that.clientSettings) - && Objects.equals(this.tokenSettings, that.tokenSettings); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.clientId, this.clientIdIssuedAt, this.clientSecret, - this.clientSecretExpiresAt, this.clientName, this.clientAuthenticationMethods, - this.authorizationGrantTypes, this.redirectUris, this.postLogoutRedirectUris, this.scopes, - this.clientSettings, this.tokenSettings); - } - - @Override - public String toString() { - return "RegisteredClient {" + "id='" + this.id + '\'' + ", clientId='" + this.clientId + '\'' + ", clientName='" - + this.clientName + '\'' + ", clientAuthenticationMethods=" + this.clientAuthenticationMethods - + ", authorizationGrantTypes=" + this.authorizationGrantTypes + ", redirectUris=" + this.redirectUris - + ", postLogoutRedirectUris=" + this.postLogoutRedirectUris + ", scopes=" + this.scopes - + ", clientSettings=" + this.clientSettings + ", tokenSettings=" + this.tokenSettings + '}'; - } - - /** - * Returns a new {@link Builder}, initialized with the provided registration - * identifier. - * @param id the identifier for the registration - * @return the {@link Builder} - */ - public static Builder withId(String id) { - Assert.hasText(id, "id cannot be empty"); - return new Builder(id); - } - - /** - * Returns a new {@link Builder}, initialized with the values from the provided - * {@link RegisteredClient}. - * @param registeredClient the {@link RegisteredClient} used for initializing the - * {@link Builder} - * @return the {@link Builder} - */ - public static Builder from(RegisteredClient registeredClient) { - Assert.notNull(registeredClient, "registeredClient cannot be null"); - return new Builder(registeredClient); - } - - /** - * A builder for {@link RegisteredClient}. - */ - public static class Builder { - - private String id; - - private String clientId; - - private Instant clientIdIssuedAt; - - private String clientSecret; - - private Instant clientSecretExpiresAt; - - private String clientName; - - private final Set clientAuthenticationMethods = new HashSet<>(); - - private final Set authorizationGrantTypes = new HashSet<>(); - - private final Set redirectUris = new HashSet<>(); - - private final Set postLogoutRedirectUris = new HashSet<>(); - - private final Set scopes = new HashSet<>(); - - private ClientSettings clientSettings; - - private TokenSettings tokenSettings; - - protected Builder(String id) { - this.id = id; - } - - protected Builder(RegisteredClient registeredClient) { - this.id = registeredClient.getId(); - this.clientId = registeredClient.getClientId(); - this.clientIdIssuedAt = registeredClient.getClientIdIssuedAt(); - this.clientSecret = registeredClient.getClientSecret(); - this.clientSecretExpiresAt = registeredClient.getClientSecretExpiresAt(); - this.clientName = registeredClient.getClientName(); - if (!CollectionUtils.isEmpty(registeredClient.getClientAuthenticationMethods())) { - this.clientAuthenticationMethods.addAll(registeredClient.getClientAuthenticationMethods()); - } - if (!CollectionUtils.isEmpty(registeredClient.getAuthorizationGrantTypes())) { - this.authorizationGrantTypes.addAll(registeredClient.getAuthorizationGrantTypes()); - } - if (!CollectionUtils.isEmpty(registeredClient.getRedirectUris())) { - this.redirectUris.addAll(registeredClient.getRedirectUris()); - } - if (!CollectionUtils.isEmpty(registeredClient.getPostLogoutRedirectUris())) { - this.postLogoutRedirectUris.addAll(registeredClient.getPostLogoutRedirectUris()); - } - if (!CollectionUtils.isEmpty(registeredClient.getScopes())) { - this.scopes.addAll(registeredClient.getScopes()); - } - this.clientSettings = ClientSettings.withSettings(registeredClient.getClientSettings().getSettings()) - .build(); - this.tokenSettings = TokenSettings.withSettings(registeredClient.getTokenSettings().getSettings()).build(); - } - - /** - * Sets the identifier for the registration. - * @param id the identifier for the registration - * @return the {@link Builder} - */ - public Builder id(String id) { - this.id = id; - return this; - } - - /** - * Sets the client identifier. - * @param clientId the client identifier - * @return the {@link Builder} - */ - public Builder clientId(String clientId) { - this.clientId = clientId; - return this; - } - - /** - * Sets the time at which the client identifier was issued. - * @param clientIdIssuedAt the time at which the client identifier was issued - * @return the {@link Builder} - */ - public Builder clientIdIssuedAt(Instant clientIdIssuedAt) { - this.clientIdIssuedAt = clientIdIssuedAt; - return this; - } - - /** - * Sets the client secret. - * @param clientSecret the client secret - * @return the {@link Builder} - */ - public Builder clientSecret(String clientSecret) { - this.clientSecret = clientSecret; - return this; - } - - /** - * Sets the time at which the client secret expires or {@code null} if it does not - * expire. - * @param clientSecretExpiresAt the time at which the client secret expires or - * {@code null} if it does not expire - * @return the {@link Builder} - */ - public Builder clientSecretExpiresAt(Instant clientSecretExpiresAt) { - this.clientSecretExpiresAt = clientSecretExpiresAt; - return this; - } - - /** - * Sets the client name. - * @param clientName the client name - * @return the {@link Builder} - */ - public Builder clientName(String clientName) { - this.clientName = clientName; - return this; - } - - /** - * Adds an {@link ClientAuthenticationMethod authentication method} the client may - * use when authenticating with the authorization server. - * @param clientAuthenticationMethod the authentication method - * @return the {@link Builder} - */ - public Builder clientAuthenticationMethod(ClientAuthenticationMethod clientAuthenticationMethod) { - this.clientAuthenticationMethods.add(clientAuthenticationMethod); - return this; - } - - /** - * A {@code Consumer} of the {@link ClientAuthenticationMethod authentication - * method(s)} allowing the ability to add, replace, or remove. - * @param clientAuthenticationMethodsConsumer a {@code Consumer} of the - * authentication method(s) - * @return the {@link Builder} - */ - public Builder clientAuthenticationMethods( - Consumer> clientAuthenticationMethodsConsumer) { - clientAuthenticationMethodsConsumer.accept(this.clientAuthenticationMethods); - return this; - } - - /** - * Adds an {@link AuthorizationGrantType authorization grant type} the client may - * use. - * @param authorizationGrantType the authorization grant type - * @return the {@link Builder} - */ - public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) { - this.authorizationGrantTypes.add(authorizationGrantType); - return this; - } - - /** - * A {@code Consumer} of the {@link AuthorizationGrantType authorization grant - * type(s)} allowing the ability to add, replace, or remove. - * @param authorizationGrantTypesConsumer a {@code Consumer} of the authorization - * grant type(s) - * @return the {@link Builder} - */ - public Builder authorizationGrantTypes(Consumer> authorizationGrantTypesConsumer) { - authorizationGrantTypesConsumer.accept(this.authorizationGrantTypes); - return this; - } - - /** - * Adds a redirect URI the client may use in a redirect-based flow. - * @param redirectUri the redirect URI - * @return the {@link Builder} - */ - public Builder redirectUri(String redirectUri) { - this.redirectUris.add(redirectUri); - return this; - } - - /** - * A {@code Consumer} of the redirect URI(s) allowing the ability to add, replace, - * or remove. - * @param redirectUrisConsumer a {@link Consumer} of the redirect URI(s) - * @return the {@link Builder} - */ - public Builder redirectUris(Consumer> redirectUrisConsumer) { - redirectUrisConsumer.accept(this.redirectUris); - return this; - } - - /** - * Adds a post logout redirect URI the client may use for logout. The - * {@code post_logout_redirect_uri} parameter is used by the client when - * requesting that the End-User's User Agent be redirected to after a logout has - * been performed. - * @param postLogoutRedirectUri the post logout redirect URI - * @return the {@link Builder} - * @since 1.1 - */ - public Builder postLogoutRedirectUri(String postLogoutRedirectUri) { - this.postLogoutRedirectUris.add(postLogoutRedirectUri); - return this; - } - - /** - * A {@code Consumer} of the post logout redirect URI(s) allowing the ability to - * add, replace, or remove. - * @param postLogoutRedirectUrisConsumer a {@link Consumer} of the post logout - * redirect URI(s) - * @return the {@link Builder} - * @since 1.1 - */ - public Builder postLogoutRedirectUris(Consumer> postLogoutRedirectUrisConsumer) { - postLogoutRedirectUrisConsumer.accept(this.postLogoutRedirectUris); - return this; - } - - /** - * Adds a scope the client may use. - * @param scope the scope - * @return the {@link Builder} - */ - public Builder scope(String scope) { - this.scopes.add(scope); - return this; - } - - /** - * A {@code Consumer} of the scope(s) allowing the ability to add, replace, or - * remove. - * @param scopesConsumer a {@link Consumer} of the scope(s) - * @return the {@link Builder} - */ - public Builder scopes(Consumer> scopesConsumer) { - scopesConsumer.accept(this.scopes); - return this; - } - - /** - * Sets the {@link ClientSettings client configuration settings}. - * @param clientSettings the client configuration settings - * @return the {@link Builder} - */ - public Builder clientSettings(ClientSettings clientSettings) { - this.clientSettings = clientSettings; - return this; - } - - /** - * Sets the {@link TokenSettings token configuration settings}. - * @param tokenSettings the token configuration settings - * @return the {@link Builder} - */ - public Builder tokenSettings(TokenSettings tokenSettings) { - this.tokenSettings = tokenSettings; - return this; - } - - /** - * Builds a new {@link RegisteredClient}. - * @return a {@link RegisteredClient} - */ - public RegisteredClient build() { - Assert.hasText(this.clientId, "clientId cannot be empty"); - Assert.notEmpty(this.authorizationGrantTypes, "authorizationGrantTypes cannot be empty"); - if (this.authorizationGrantTypes.contains(AuthorizationGrantType.AUTHORIZATION_CODE)) { - Assert.notEmpty(this.redirectUris, "redirectUris cannot be empty"); - } - if (!StringUtils.hasText(this.clientName)) { - this.clientName = this.id; - } - if (CollectionUtils.isEmpty(this.clientAuthenticationMethods)) { - this.clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - } - if (this.clientSettings == null) { - ClientSettings.Builder builder = ClientSettings.builder(); - if (isPublicClientType()) { - // @formatter:off - builder - .requireProofKey(true) - .requireAuthorizationConsent(true); - // @formatter:on - } - this.clientSettings = builder.build(); - } - if (this.tokenSettings == null) { - this.tokenSettings = TokenSettings.builder().build(); - } - validateScopes(); - validateRedirectUris(); - validatePostLogoutRedirectUris(); - return create(); - } - - private boolean isPublicClientType() { - return this.authorizationGrantTypes.contains(AuthorizationGrantType.AUTHORIZATION_CODE) - && this.clientAuthenticationMethods.size() == 1 - && this.clientAuthenticationMethods.contains(ClientAuthenticationMethod.NONE); - } - - private RegisteredClient create() { - RegisteredClient registeredClient = new RegisteredClient(); - - registeredClient.id = this.id; - registeredClient.clientId = this.clientId; - registeredClient.clientIdIssuedAt = this.clientIdIssuedAt; - registeredClient.clientSecret = this.clientSecret; - registeredClient.clientSecretExpiresAt = this.clientSecretExpiresAt; - registeredClient.clientName = this.clientName; - registeredClient.clientAuthenticationMethods = Collections - .unmodifiableSet(new HashSet<>(this.clientAuthenticationMethods)); - registeredClient.authorizationGrantTypes = Collections - .unmodifiableSet(new HashSet<>(this.authorizationGrantTypes)); - registeredClient.redirectUris = Collections.unmodifiableSet(new HashSet<>(this.redirectUris)); - registeredClient.postLogoutRedirectUris = Collections - .unmodifiableSet(new HashSet<>(this.postLogoutRedirectUris)); - registeredClient.scopes = Collections.unmodifiableSet(new HashSet<>(this.scopes)); - registeredClient.clientSettings = this.clientSettings; - registeredClient.tokenSettings = this.tokenSettings; - - return registeredClient; - } - - private void validateScopes() { - if (CollectionUtils.isEmpty(this.scopes)) { - return; - } - - for (String scope : this.scopes) { - Assert.isTrue(validateScope(scope), "scope \"" + scope + "\" contains invalid characters"); - } - } - - private static boolean validateScope(String scope) { - return scope == null || scope.chars() - .allMatch((c) -> withinTheRangeOf(c, 0x21, 0x21) || withinTheRangeOf(c, 0x23, 0x5B) - || withinTheRangeOf(c, 0x5D, 0x7E)); - } - - private static boolean withinTheRangeOf(int c, int min, int max) { - return c >= min && c <= max; - } - - private void validateRedirectUris() { - if (CollectionUtils.isEmpty(this.redirectUris)) { - return; - } - - for (String redirectUri : this.redirectUris) { - Assert.isTrue(validateRedirectUri(redirectUri), - "redirect_uri \"" + redirectUri + "\" is not a valid redirect URI or contains fragment"); - } - } - - private void validatePostLogoutRedirectUris() { - if (CollectionUtils.isEmpty(this.postLogoutRedirectUris)) { - return; - } - - for (String postLogoutRedirectUri : this.postLogoutRedirectUris) { - Assert.isTrue(validateRedirectUri(postLogoutRedirectUri), "post_logout_redirect_uri \"" - + postLogoutRedirectUri + "\" is not a valid post logout redirect URI or contains fragment"); - } - } - - private static boolean validateRedirectUri(String redirectUri) { - try { - URI validRedirectUri = new URI(redirectUri); - return validRedirectUri.getFragment() == null; - } - catch (URISyntaxException ex) { - return false; - } - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java deleted file mode 100644 index 66ffdeb7a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.client; - -import org.springframework.lang.Nullable; - -/** - * A repository for OAuth 2.0 {@link RegisteredClient}(s). - * - * @author Joe Grandja - * @author Anoop Garlapati - * @author Ovidiu Popa - * @since 0.0.1 - * @see RegisteredClient - */ -public interface RegisteredClientRepository { - - /** - * Saves the registered client. - * - *

- * IMPORTANT: Sensitive information should be encoded externally from the - * implementation, e.g. {@link RegisteredClient#getClientSecret()} - * @param registeredClient the {@link RegisteredClient} - */ - void save(RegisteredClient registeredClient); - - /** - * Returns the registered client identified by the provided {@code id}, or - * {@code null} if not found. - * @param id the registration identifier - * @return the {@link RegisteredClient} if found, otherwise {@code null} - */ - @Nullable - RegisteredClient findById(String id); - - /** - * Returns the registered client identified by the provided {@code clientId}, or - * {@code null} if not found. - * @param clientId the client identifier - * @return the {@link RegisteredClient} if found, otherwise {@code null} - */ - @Nullable - RegisteredClient findByClientId(String clientId); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java deleted file mode 100644 index 6a527ae31..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configuration; - -import java.util.HashSet; -import java.util.Set; - -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.JWSKeySelector; -import com.nimbusds.jose.proc.JWSVerificationKeySelector; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; -import com.nimbusds.jwt.proc.DefaultJWTProcessor; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.SecurityFilterChain; - -/** - * {@link Configuration} for OAuth 2.0 Authorization Server support. - * - * @author Joe Grandja - * @since 0.0.1 - * @see OAuth2AuthorizationServerConfigurer - */ -@Configuration(proxyBeanMethods = false) -public class OAuth2AuthorizationServerConfiguration { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, Customizer.withDefaults()) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - // @formatter:on - return http.build(); - } - - public static JwtDecoder jwtDecoder(JWKSource jwkSource) { - Set jwsAlgs = new HashSet<>(); - jwsAlgs.addAll(JWSAlgorithm.Family.RSA); - jwsAlgs.addAll(JWSAlgorithm.Family.EC); - jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA); - ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); - JWSKeySelector jwsKeySelector = new JWSVerificationKeySelector<>(jwsAlgs, jwkSource); - jwtProcessor.setJWSKeySelector(jwsKeySelector); - // Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it - // instead - jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { - }); - return new NimbusJwtDecoder(jwtProcessor); - } - - @Bean - RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() { - RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor(); - postProcessor.addBeanDefinition(AuthorizationServerSettings.class, - () -> AuthorizationServerSettings.builder().build()); - return postProcessor; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessor.java deleted file mode 100644 index 8ca044feb..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.config.annotation.web.configuration; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.annotation.AnnotationBeanNameGenerator; - -/** - * Post processor to register one or more bean definitions on container initialization, if - * not already present. - * - * @author Steve Riesenberg - * @since 0.2.0 - */ -final class RegisterMissingBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware { - - private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator(); - - private final List beanDefinitions = new ArrayList<>(); - - private BeanFactory beanFactory; - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - for (AbstractBeanDefinition beanDefinition : this.beanDefinitions) { - String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( - (ListableBeanFactory) this.beanFactory, beanDefinition.getBeanClass(), false, false); - if (beanNames.length == 0) { - String beanName = this.beanNameGenerator.generateBeanName(beanDefinition, registry); - registry.registerBeanDefinition(beanName, beanDefinition); - } - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - } - - void addBeanDefinition(Class beanClass, Supplier beanSupplier) { - this.beanDefinitions.add(new RootBeanDefinition(beanClass, beanSupplier)); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AbstractOAuth2Configurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AbstractOAuth2Configurer.java deleted file mode 100644 index 12a6f2d59..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AbstractOAuth2Configurer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Base configurer for an OAuth 2.0 component (e.g. protocol endpoint). - * - * @author Joe Grandja - * @since 0.1.2 - */ -abstract class AbstractOAuth2Configurer { - - private final ObjectPostProcessor objectPostProcessor; - - AbstractOAuth2Configurer(ObjectPostProcessor objectPostProcessor) { - this.objectPostProcessor = objectPostProcessor; - } - - abstract void init(HttpSecurity httpSecurity); - - abstract void configure(HttpSecurity httpSecurity); - - abstract RequestMatcher getRequestMatcher(); - - protected final T postProcess(T object) { - return (T) this.objectPostProcessor.postProcess(object); - } - - protected final ObjectPostProcessor getObjectPostProcessor() { - return this.objectPostProcessor; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilter.java deleted file mode 100644 index f31b0fd0c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilter.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.util.UrlUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Filter} that associates the {@link AuthorizationServerContext} to the - * {@link AuthorizationServerContextHolder}. - * - * @author Joe Grandja - * @since 0.2.2 - * @see AuthorizationServerContext - * @see AuthorizationServerContextHolder - * @see AuthorizationServerSettings - */ -final class AuthorizationServerContextFilter extends OncePerRequestFilter { - - private final AuthorizationServerSettings authorizationServerSettings; - - private final IssuerResolver issuerResolver; - - AuthorizationServerContextFilter(AuthorizationServerSettings authorizationServerSettings) { - Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null"); - this.authorizationServerSettings = authorizationServerSettings; - this.issuerResolver = new IssuerResolver(authorizationServerSettings); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - try { - String issuer = this.issuerResolver.resolve(request); - AuthorizationServerContext authorizationServerContext = new DefaultAuthorizationServerContext(issuer, - this.authorizationServerSettings); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - filterChain.doFilter(request, response); - } - finally { - AuthorizationServerContextHolder.resetContext(); - } - } - - private static final class IssuerResolver { - - private final String issuer; - - private final Set endpointUris; - - private IssuerResolver(AuthorizationServerSettings authorizationServerSettings) { - if (authorizationServerSettings.getIssuer() != null) { - this.issuer = authorizationServerSettings.getIssuer(); - this.endpointUris = Collections.emptySet(); - } - else { - this.issuer = null; - this.endpointUris = new HashSet<>(); - this.endpointUris.add("/.well-known/oauth-authorization-server"); - this.endpointUris.add("/.well-known/openid-configuration"); - for (Map.Entry setting : authorizationServerSettings.getSettings().entrySet()) { - if (setting.getKey().endsWith("-endpoint")) { - this.endpointUris.add((String) setting.getValue()); - } - } - } - } - - private String resolve(HttpServletRequest request) { - if (this.issuer != null) { - return this.issuer; - } - - // Resolve Issuer Identifier dynamically from request - String path = request.getRequestURI(); - if (!StringUtils.hasText(path)) { - path = ""; - } - else { - for (String endpointUri : this.endpointUris) { - if (path.contains(endpointUri)) { - path = path.replace(endpointUri, ""); - break; - } - } - } - - // @formatter:off - return UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)) - .replacePath(path) - .replaceQuery(null) - .fragment(null) - .build() - .toUriString(); - // @formatter:on - } - - } - - private static final class DefaultAuthorizationServerContext implements AuthorizationServerContext { - - private final String issuer; - - private final AuthorizationServerSettings authorizationServerSettings; - - private DefaultAuthorizationServerContext(String issuer, - AuthorizationServerSettings authorizationServerSettings) { - this.issuer = issuer; - this.authorizationServerSettings = authorizationServerSettings; - } - - @Override - public String getIssuer() { - return this.issuer; - } - - @Override - public AuthorizationServerSettings getAuthorizationServerSettings() { - return this.authorizationServerSettings; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizers.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizers.java deleted file mode 100644 index 155f860a9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizers.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.security.MessageDigest; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import com.nimbusds.jose.jwk.JWK; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.util.CollectionUtils; - -/** - * @author Joe Grandja - * @author Steve Riesenberg - * @since 1.3 - */ -final class DefaultOAuth2TokenCustomizers { - - private DefaultOAuth2TokenCustomizers() { - } - - static OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> context.getClaims().claims((claims) -> customize(context, claims)); - } - - static OAuth2TokenCustomizer accessTokenCustomizer() { - return (context) -> context.getClaims().claims((claims) -> customize(context, claims)); - } - - private static void customize(OAuth2TokenContext tokenContext, Map claims) { - Map cnfClaims = null; - - // Add 'cnf' claim for Mutual-TLS Client Certificate-Bound Access Tokens - if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenContext.getTokenType()) - && tokenContext.getAuthorizationGrant() != null && tokenContext.getAuthorizationGrant() - .getPrincipal() instanceof OAuth2ClientAuthenticationToken clientAuthentication) { - - if ((ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) - || ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH - .equals(clientAuthentication.getClientAuthenticationMethod())) - && tokenContext.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) { - - X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials(); - try { - String sha256Thumbprint = computeSHA256Thumbprint(clientCertificateChain[0]); - cnfClaims = new HashMap<>(); - cnfClaims.put("x5t#S256", sha256Thumbprint); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Failed to compute SHA-256 Thumbprint for client X509Certificate.", null); - throw new OAuth2AuthenticationException(error, ex); - } - } - } - - // Add 'cnf' claim for OAuth 2.0 Demonstrating Proof of Possession (DPoP) - Jwt dPoPProofJwt = tokenContext.get(OAuth2TokenContext.DPOP_PROOF_KEY); - if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenContext.getTokenType()) && dPoPProofJwt != null) { - JWK jwk = null; - @SuppressWarnings("unchecked") - Map jwkJson = (Map) dPoPProofJwt.getHeaders().get("jwk"); - try { - jwk = JWK.parse(jwkJson); - } - catch (Exception ignored) { - } - if (jwk == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_DPOP_PROOF, - "jwk header is missing or invalid.", null); - throw new OAuth2AuthenticationException(error); - } - - try { - String sha256Thumbprint = jwk.computeThumbprint().toString(); - if (cnfClaims == null) { - cnfClaims = new HashMap<>(); - } - cnfClaims.put("jkt", sha256Thumbprint); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Failed to compute SHA-256 Thumbprint for DPoP Proof PublicKey.", null); - throw new OAuth2AuthenticationException(error, ex); - } - } - - if (!CollectionUtils.isEmpty(cnfClaims)) { - claims.put("cnf", cnfClaims); - } - - // Add 'act' claim for delegation use case of Token Exchange Grant. - // If more than one actor is present, we create a chain of delegation by nesting - // "act" claims. - if (tokenContext - .getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) { - Map currentClaims = claims; - for (OAuth2TokenExchangeActor actor : compositeAuthenticationToken.getActors()) { - Map actorClaims = actor.getClaims(); - Map actClaim = new LinkedHashMap<>(); - actClaim.put(OAuth2TokenClaimNames.ISS, actorClaims.get(OAuth2TokenClaimNames.ISS)); - actClaim.put(OAuth2TokenClaimNames.SUB, actorClaims.get(OAuth2TokenClaimNames.SUB)); - currentClaims.put("act", Collections.unmodifiableMap(actClaim)); - currentClaims = actClaim; - } - } - } - - private static String computeSHA256Thumbprint(X509Certificate x509Certificate) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(x509Certificate.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java deleted file mode 100644 index a8bce723a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Configurer for the OAuth 2.0 Authorization Endpoint. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationServerConfigurer#authorizationEndpoint - * @see OAuth2AuthorizationEndpointFilter - */ -public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List authorizationRequestConverters = new ArrayList<>(); - - private Consumer> authorizationRequestConvertersConsumer = ( - authorizationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler authorizationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - private String consentPage; - - private Consumer authorizationCodeRequestAuthenticationValidator; - - private SessionAuthenticationStrategy sessionAuthenticationStrategy; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2AuthorizationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract an - * Authorization Request (or Consent) from {@link HttpServletRequest} to an instance - * of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} or - * {@link OAuth2AuthorizationConsentAuthenticationToken} used for authenticating the - * request. - * @param authorizationRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract an Authorization Request (or Consent) from - * {@link HttpServletRequest} - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - */ - public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter( - AuthenticationConverter authorizationRequestConverter) { - Assert.notNull(authorizationRequestConverter, "authorizationRequestConverter cannot be null"); - this.authorizationRequestConverters.add(authorizationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authorizationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param authorizationRequestConvertersConsumer the {@code Consumer} providing access - * to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverters( - Consumer> authorizationRequestConvertersConsumer) { - Assert.notNull(authorizationRequestConvertersConsumer, "authorizationRequestConvertersConsumer cannot be null"); - this.authorizationRequestConvertersConsumer = authorizationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - */ - public OAuth2AuthorizationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2AuthorizationEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and returning the - * {@link OAuth2AuthorizationResponse Authorization Response}. - * @param authorizationResponseHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - */ - public OAuth2AuthorizationEndpointConfigurer authorizationResponseHandler( - AuthenticationSuccessHandler authorizationResponseHandler) { - this.authorizationResponseHandler = authorizationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationException} and returning the - * {@link OAuth2Error Error Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException} - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - */ - public OAuth2AuthorizationEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - /** - * Specify the URI to redirect Resource Owners to if consent is required during the - * {@code authorization_code} flow. A default consent page will be generated when this - * attribute is not specified. - * - * If a URI is specified, applications are required to process the specified URI to - * generate a consent page. The query string will contain the following parameters: - * - *
    - *
  • {@code client_id} - the client identifier
  • - *
  • {@code scope} - a space-delimited list of scopes present in the authorization - * request
  • - *
  • {@code state} - a CSRF protection token
  • - *
- * - * In general, the consent page should create a form that submits a request with the - * following requirements: - * - *
    - *
  • It must be an HTTP POST
  • - *
  • It must be submitted to - * {@link AuthorizationServerSettings#getAuthorizationEndpoint()}
  • - *
  • It must include the received {@code client_id} as an HTTP parameter
  • - *
  • It must include the received {@code state} as an HTTP parameter
  • - *
  • It must include the list of {@code scope}s the {@code Resource Owner} consented - * to as an HTTP parameter
  • - *
- * @param consentPage the URI of the custom consent page to redirect to if consent is - * required (e.g. "/oauth2/consent") - * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration - */ - public OAuth2AuthorizationEndpointConfigurer consentPage(String consentPage) { - this.consentPage = consentPage; - return this; - } - - void addAuthorizationCodeRequestAuthenticationValidator( - Consumer authenticationValidator) { - this.authorizationCodeRequestAuthenticationValidator = (this.authorizationCodeRequestAuthenticationValidator == null) - ? authenticationValidator - : this.authorizationCodeRequestAuthenticationValidator.andThen(authenticationValidator); - } - - void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) { - this.sessionAuthenticationStrategy = sessionAuthenticationStrategy; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) - : authorizationServerSettings.getAuthorizationEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, authorizationEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, authorizationEndpointUri)); - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) - : authorizationServerSettings.getAuthorizationEndpoint(); - OAuth2AuthorizationEndpointFilter authorizationEndpointFilter = new OAuth2AuthorizationEndpointFilter( - authenticationManager, authorizationEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.authorizationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.authorizationRequestConverters); - } - this.authorizationRequestConvertersConsumer.accept(authenticationConverters); - authorizationEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.authorizationResponseHandler != null) { - authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler); - } - if (this.errorResponseHandler != null) { - authorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - if (StringUtils.hasText(this.consentPage)) { - authorizationEndpointFilter.setConsentPage(this.consentPage); - } - if (this.sessionAuthenticationStrategy != null) { - authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy); - } - httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2AuthorizationCodeRequestAuthenticationConverter()); - authenticationConverters.add(new OAuth2AuthorizationConsentAuthenticationConverter()); - - return authenticationConverters; - } - - private List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider = new OAuth2AuthorizationCodeRequestAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity)); - if (this.authorizationCodeRequestAuthenticationValidator != null) { - authorizationCodeRequestAuthenticationProvider - .setAuthenticationValidator(new OAuth2AuthorizationCodeRequestAuthenticationValidator() - .andThen(this.authorizationCodeRequestAuthenticationValidator)); - } - authenticationProviders.add(authorizationCodeRequestAuthenticationProvider); - - OAuth2AuthorizationConsentAuthenticationProvider authorizationConsentAuthenticationProvider = new OAuth2AuthorizationConsentAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity)); - authenticationProviders.add(authorizationConsentAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java deleted file mode 100644 index 011d77cf9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java +++ /dev/null @@ -1,506 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.net.URI; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.source.JWKSource; - -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.GenericApplicationListenerAdapter; -import org.springframework.context.event.SmartApplicationListener; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; -import org.springframework.security.context.DelegatingApplicationListener; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter; -import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.context.SecurityContextHolderFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @author Gerardo Roza - * @author Ovidiu Popa - * @author Gaurav Tiwari - * @since 0.0.1 - * @see AbstractHttpConfigurer - * @see OAuth2ClientAuthenticationConfigurer - * @see OAuth2AuthorizationServerMetadataEndpointConfigurer - * @see OAuth2AuthorizationEndpointConfigurer - * @see OAuth2PushedAuthorizationRequestEndpointConfigurer - * @see OAuth2TokenEndpointConfigurer - * @see OAuth2TokenIntrospectionEndpointConfigurer - * @see OAuth2TokenRevocationEndpointConfigurer - * @see OAuth2DeviceAuthorizationEndpointConfigurer - * @see OAuth2DeviceVerificationEndpointConfigurer - * @see OidcConfigurer - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2AuthorizationConsentService - * @see NimbusJwkSetEndpointFilter - */ -public final class OAuth2AuthorizationServerConfigurer - extends AbstractHttpConfigurer { - - private final Map, AbstractOAuth2Configurer> configurers = createConfigurers(); - - private RequestMatcher endpointsMatcher; - - /** - * Returns a new instance of {@link OAuth2AuthorizationServerConfigurer} for - * configuring. - * @return a new instance of {@link OAuth2AuthorizationServerConfigurer} for - * configuring - * @since 1.4 - */ - public static OAuth2AuthorizationServerConfigurer authorizationServer() { - return new OAuth2AuthorizationServerConfigurer(); - } - - /** - * Sets the repository of registered clients. - * @param registeredClientRepository the repository of registered clients - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer registeredClientRepository( - RegisteredClientRepository registeredClientRepository) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository); - return this; - } - - /** - * Sets the authorization service. - * @param authorizationService the authorization service - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer authorizationService(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService); - return this; - } - - /** - * Sets the authorization consent service. - * @param authorizationConsentService the authorization consent service - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer authorizationConsentService( - OAuth2AuthorizationConsentService authorizationConsentService) { - Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null"); - getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService); - return this; - } - - /** - * Sets the authorization server settings. - * @param authorizationServerSettings the authorization server settings - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer authorizationServerSettings( - AuthorizationServerSettings authorizationServerSettings) { - Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null"); - getBuilder().setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings); - return this; - } - - /** - * Sets the token generator. - * @param tokenGenerator the token generator - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 0.2.3 - */ - public OAuth2AuthorizationServerConfigurer tokenGenerator( - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - getBuilder().setSharedObject(OAuth2TokenGenerator.class, tokenGenerator); - return this; - } - - /** - * Configures OAuth 2.0 Client Authentication. - * @param clientAuthenticationCustomizer the {@link Customizer} providing access to - * the {@link OAuth2ClientAuthenticationConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer clientAuthentication( - Customizer clientAuthenticationCustomizer) { - clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Authorization Server Metadata Endpoint. - * @param authorizationServerMetadataEndpointCustomizer the {@link Customizer} - * providing access to the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2AuthorizationServerConfigurer authorizationServerMetadataEndpoint( - Customizer authorizationServerMetadataEndpointCustomizer) { - authorizationServerMetadataEndpointCustomizer - .customize(getConfigurer(OAuth2AuthorizationServerMetadataEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Authorization Endpoint. - * @param authorizationEndpointCustomizer the {@link Customizer} providing access to - * the {@link OAuth2AuthorizationEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer authorizationEndpoint( - Customizer authorizationEndpointCustomizer) { - authorizationEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Pushed Authorization Request Endpoint. - * @param pushedAuthorizationRequestEndpointCustomizer the {@link Customizer} - * providing access to the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 1.5 - */ - public OAuth2AuthorizationServerConfigurer pushedAuthorizationRequestEndpoint( - Customizer pushedAuthorizationRequestEndpointCustomizer) { - OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer( - OAuth2PushedAuthorizationRequestEndpointConfigurer.class); - if (pushedAuthorizationRequestEndpointConfigurer == null) { - addConfigurer(OAuth2PushedAuthorizationRequestEndpointConfigurer.class, - new OAuth2PushedAuthorizationRequestEndpointConfigurer(this::postProcess)); - pushedAuthorizationRequestEndpointConfigurer = getConfigurer( - OAuth2PushedAuthorizationRequestEndpointConfigurer.class); - } - pushedAuthorizationRequestEndpointCustomizer.customize(pushedAuthorizationRequestEndpointConfigurer); - return this; - } - - /** - * Configures the OAuth 2.0 Token Endpoint. - * @param tokenEndpointCustomizer the {@link Customizer} providing access to the - * {@link OAuth2TokenEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer tokenEndpoint( - Customizer tokenEndpointCustomizer) { - tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Token Introspection Endpoint. - * @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access - * to the {@link OAuth2TokenIntrospectionEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 0.2.3 - */ - public OAuth2AuthorizationServerConfigurer tokenIntrospectionEndpoint( - Customizer tokenIntrospectionEndpointCustomizer) { - tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Token Revocation Endpoint. - * @param tokenRevocationEndpointCustomizer the {@link Customizer} providing access to - * the {@link OAuth2TokenRevocationEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 0.2.2 - */ - public OAuth2AuthorizationServerConfigurer tokenRevocationEndpoint( - Customizer tokenRevocationEndpointCustomizer) { - tokenRevocationEndpointCustomizer.customize(getConfigurer(OAuth2TokenRevocationEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Device Authorization Endpoint. - * @param deviceAuthorizationEndpointCustomizer the {@link Customizer} providing - * access to the {@link OAuth2DeviceAuthorizationEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 1.1 - */ - public OAuth2AuthorizationServerConfigurer deviceAuthorizationEndpoint( - Customizer deviceAuthorizationEndpointCustomizer) { - deviceAuthorizationEndpointCustomizer - .customize(getConfigurer(OAuth2DeviceAuthorizationEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OAuth 2.0 Device Verification Endpoint. - * @param deviceVerificationEndpointCustomizer the {@link Customizer} providing access - * to the {@link OAuth2DeviceVerificationEndpointConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - * @since 1.1 - */ - public OAuth2AuthorizationServerConfigurer deviceVerificationEndpoint( - Customizer deviceVerificationEndpointCustomizer) { - deviceVerificationEndpointCustomizer.customize(getConfigurer(OAuth2DeviceVerificationEndpointConfigurer.class)); - return this; - } - - /** - * Configures OpenID Connect 1.0 support (disabled by default). - * @param oidcCustomizer the {@link Customizer} providing access to the - * {@link OidcConfigurer} - * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration - */ - public OAuth2AuthorizationServerConfigurer oidc(Customizer oidcCustomizer) { - OidcConfigurer oidcConfigurer = getConfigurer(OidcConfigurer.class); - if (oidcConfigurer == null) { - addConfigurer(OidcConfigurer.class, new OidcConfigurer(this::postProcess)); - oidcConfigurer = getConfigurer(OidcConfigurer.class); - } - oidcCustomizer.customize(oidcConfigurer); - return this; - } - - /** - * Returns a {@link RequestMatcher} for the authorization server endpoints. - * @return a {@link RequestMatcher} for the authorization server endpoints - */ - public RequestMatcher getEndpointsMatcher() { - // Return a deferred RequestMatcher - // since endpointsMatcher is constructed in init(HttpSecurity). - return (request) -> this.endpointsMatcher.matches(request); - } - - @Override - public void init(HttpSecurity httpSecurity) throws Exception { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - validateAuthorizationServerSettings(authorizationServerSettings); - - if (isOidcEnabled()) { - // Add OpenID Connect session tracking capabilities. - initSessionRegistry(httpSecurity); - SessionRegistry sessionRegistry = httpSecurity.getSharedObject(SessionRegistry.class); - OAuth2AuthorizationEndpointConfigurer authorizationEndpointConfigurer = getConfigurer( - OAuth2AuthorizationEndpointConfigurer.class); - authorizationEndpointConfigurer.setSessionAuthenticationStrategy((authentication, request, response) -> { - if (authentication instanceof OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) { - if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) { - if (sessionRegistry.getSessionInformation(request.getSession().getId()) == null) { - sessionRegistry.registerNewSession(request.getSession().getId(), - ((Authentication) authorizationCodeRequestAuthentication.getPrincipal()) - .getPrincipal()); - } - } - } - }); - } - else { - // OpenID Connect is disabled. - // Add an authentication validator that rejects authentication requests. - Consumer oidcAuthenticationRequestValidator = ( - authenticationContext) -> { - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext - .getAuthentication(); - if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE, - "OpenID Connect 1.0 authentication requests are restricted.", - "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, - authorizationCodeRequestAuthentication); - } - }; - OAuth2AuthorizationEndpointConfigurer authorizationEndpointConfigurer = getConfigurer( - OAuth2AuthorizationEndpointConfigurer.class); - authorizationEndpointConfigurer - .addAuthorizationCodeRequestAuthenticationValidator(oidcAuthenticationRequestValidator); - OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer( - OAuth2PushedAuthorizationRequestEndpointConfigurer.class); - if (pushedAuthorizationRequestEndpointConfigurer != null) { - pushedAuthorizationRequestEndpointConfigurer - .addAuthorizationCodeRequestAuthenticationValidator(oidcAuthenticationRequestValidator); - } - } - - List requestMatchers = new ArrayList<>(); - this.configurers.values().forEach((configurer) -> { - configurer.init(httpSecurity); - requestMatchers.add(configurer.getRequestMatcher()); - }); - String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) - : authorizationServerSettings.getJwkSetEndpoint(); - requestMatchers.add(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, jwkSetEndpointUri)); - this.endpointsMatcher = new OrRequestMatcher(requestMatchers); - - ExceptionHandlingConfigurer exceptionHandling = httpSecurity - .getConfigurer(ExceptionHandlingConfigurer.class); - if (exceptionHandling != null) { - List preferredMatchers = new ArrayList<>(); - preferredMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class)); - preferredMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class)); - preferredMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class)); - preferredMatchers.add(getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class)); - RequestMatcher preferredMatcher = getRequestMatcher( - OAuth2PushedAuthorizationRequestEndpointConfigurer.class); - if (preferredMatcher != null) { - preferredMatchers.add(preferredMatcher); - } - exceptionHandling.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), - new OrRequestMatcher(preferredMatchers)); - } - - httpSecurity.csrf((csrf) -> csrf.ignoringRequestMatchers(this.endpointsMatcher)); - - OidcConfigurer oidcConfigurer = getConfigurer(OidcConfigurer.class); - if (oidcConfigurer != null) { - if (oidcConfigurer.getConfigurer(OidcUserInfoEndpointConfigurer.class) != null - || oidcConfigurer.getConfigurer(OidcClientRegistrationEndpointConfigurer.class) != null) { - httpSecurity - // Accept access tokens for User Info and/or Client Registration - .oauth2ResourceServer( - (oauth2ResourceServer) -> oauth2ResourceServer.jwt(Customizer.withDefaults())); - - } - } - } - - @Override - public void configure(HttpSecurity httpSecurity) { - this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity)); - - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - AuthorizationServerContextFilter authorizationServerContextFilter = new AuthorizationServerContextFilter( - authorizationServerSettings); - httpSecurity.addFilterAfter(postProcess(authorizationServerContextFilter), SecurityContextHolderFilter.class); - - JWKSource jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity); - if (jwkSource != null) { - String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) - : authorizationServerSettings.getJwkSetEndpoint(); - NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(jwkSource, - jwkSetEndpointUri); - httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - } - - private boolean isOidcEnabled() { - return getConfigurer(OidcConfigurer.class) != null; - } - - private Map, AbstractOAuth2Configurer> createConfigurers() { - Map, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>(); - configurers.put(OAuth2ClientAuthenticationConfigurer.class, - new OAuth2ClientAuthenticationConfigurer(this::postProcess)); - configurers.put(OAuth2AuthorizationServerMetadataEndpointConfigurer.class, - new OAuth2AuthorizationServerMetadataEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2AuthorizationEndpointConfigurer.class, - new OAuth2AuthorizationEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, - new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, - new OAuth2TokenRevocationEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2DeviceAuthorizationEndpointConfigurer.class, - new OAuth2DeviceAuthorizationEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2DeviceVerificationEndpointConfigurer.class, - new OAuth2DeviceVerificationEndpointConfigurer(this::postProcess)); - return configurers; - } - - @SuppressWarnings("unchecked") - private T getConfigurer(Class type) { - return (T) this.configurers.get(type); - } - - private void addConfigurer(Class configurerType, T configurer) { - this.configurers.put(configurerType, configurer); - } - - private RequestMatcher getRequestMatcher(Class configurerType) { - T configurer = getConfigurer(configurerType); - return (configurer != null) ? configurer.getRequestMatcher() : null; - } - - private static void validateAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) { - if (authorizationServerSettings.getIssuer() != null) { - URI issuerUri; - try { - issuerUri = new URI(authorizationServerSettings.getIssuer()); - issuerUri.toURL(); - } - catch (Exception ex) { - throw new IllegalArgumentException("issuer must be a valid URL", ex); - } - // rfc8414 https://datatracker.ietf.org/doc/html/rfc8414#section-2 - if (issuerUri.getQuery() != null || issuerUri.getFragment() != null) { - throw new IllegalArgumentException("issuer cannot contain query or fragment component"); - } - } - } - - private static void initSessionRegistry(HttpSecurity httpSecurity) { - SessionRegistry sessionRegistry = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, SessionRegistry.class); - if (sessionRegistry == null) { - sessionRegistry = new SessionRegistryImpl(); - registerDelegateApplicationListener(httpSecurity, (SessionRegistryImpl) sessionRegistry); - } - httpSecurity.setSharedObject(SessionRegistry.class, sessionRegistry); - } - - private static void registerDelegateApplicationListener(HttpSecurity httpSecurity, - ApplicationListener delegate) { - DelegatingApplicationListener delegatingApplicationListener = OAuth2ConfigurerUtils - .getOptionalBean(httpSecurity, DelegatingApplicationListener.class); - if (delegatingApplicationListener == null) { - return; - } - SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate); - delegatingApplicationListener.addListener(smartListener); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java deleted file mode 100644 index 546963a78..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.function.Consumer; - -import org.springframework.http.HttpMethod; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Configurer for the OAuth 2.0 Authorization Server Metadata Endpoint. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OAuth2AuthorizationServerConfigurer#authorizationServerMetadataEndpoint - * @see OAuth2AuthorizationServerMetadataEndpointFilter - */ -public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private Consumer authorizationServerMetadataCustomizer; - - private Consumer defaultAuthorizationServerMetadataCustomizer; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2AuthorizationServerMetadataEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationServerMetadata.Builder} allowing the ability to customize - * the claims of the Authorization Server's configuration. - * @param authorizationServerMetadataCustomizer the {@code Consumer} providing access - * to the {@link OAuth2AuthorizationServerMetadata.Builder} - * @return the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} for further - * configuration - */ - public OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataCustomizer( - Consumer authorizationServerMetadataCustomizer) { - this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer; - return this; - } - - void addDefaultAuthorizationServerMetadataCustomizer( - Consumer defaultAuthorizationServerMetadataCustomizer) { - this.defaultAuthorizationServerMetadataCustomizer = (this.defaultAuthorizationServerMetadataCustomizer == null) - ? defaultAuthorizationServerMetadataCustomizer : this.defaultAuthorizationServerMetadataCustomizer - .andThen(defaultAuthorizationServerMetadataCustomizer); - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String authorizationServerMetadataEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? "/.well-known/oauth-authorization-server/**" : "/.well-known/oauth-authorization-server"; - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, authorizationServerMetadataEndpointUri); - } - - @Override - void configure(HttpSecurity httpSecurity) { - OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter = new OAuth2AuthorizationServerMetadataEndpointFilter(); - Consumer authorizationServerMetadataCustomizer = getAuthorizationServerMetadataCustomizer(); - if (authorizationServerMetadataCustomizer != null) { - authorizationServerMetadataEndpointFilter - .setAuthorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer); - } - httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - - private Consumer getAuthorizationServerMetadataCustomizer() { - Consumer authorizationServerMetadataCustomizer = null; - if (this.defaultAuthorizationServerMetadataCustomizer != null - || this.authorizationServerMetadataCustomizer != null) { - if (this.defaultAuthorizationServerMetadataCustomizer != null) { - authorizationServerMetadataCustomizer = this.defaultAuthorizationServerMetadataCustomizer; - } - if (this.authorizationServerMetadataCustomizer != null) { - authorizationServerMetadataCustomizer = (authorizationServerMetadataCustomizer != null) - ? authorizationServerMetadataCustomizer.andThen(this.authorizationServerMetadataCustomizer) - : this.authorizationServerMetadataCustomizer; - } - } - return authorizationServerMetadataCustomizer; - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java deleted file mode 100644 index 58fc8418f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.X509ClientCertificateAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for OAuth 2.0 Client Authentication. - * - * @author Joe Grandja - * @since 0.2.0 - * @see OAuth2AuthorizationServerConfigurer#clientAuthentication - * @see OAuth2ClientAuthenticationFilter - */ -public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List authenticationConverters = new ArrayList<>(); - - private Consumer> authenticationConvertersConsumer = (authenticationConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler authenticationSuccessHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2ClientAuthenticationConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract client - * credentials from {@link HttpServletRequest} to an instance of - * {@link OAuth2ClientAuthenticationToken} used for authenticating the client. - * @param authenticationConverter an {@link AuthenticationConverter} used when - * attempting to extract client credentials from {@link HttpServletRequest} - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - */ - public OAuth2ClientAuthenticationConfigurer authenticationConverter( - AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverters.add(authenticationConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param authenticationConvertersConsumer the {@code Consumer} providing access to - * the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2ClientAuthenticationConfigurer authenticationConverters( - Consumer> authenticationConvertersConsumer) { - Assert.notNull(authenticationConvertersConsumer, "authenticationConvertersConsumer cannot be null"); - this.authenticationConvertersConsumer = authenticationConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OAuth2ClientAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OAuth2ClientAuthenticationToken} - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - */ - public OAuth2ClientAuthenticationConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2ClientAuthenticationConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling a successful client - * authentication and associating the {@link OAuth2ClientAuthenticationToken} to the - * {@link SecurityContext}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling a successful client authentication - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - */ - public OAuth2ClientAuthenticationConfigurer authenticationSuccessHandler( - AuthenticationSuccessHandler authenticationSuccessHandler) { - this.authenticationSuccessHandler = authenticationSuccessHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling a failed client - * authentication and returning the {@link OAuth2Error Error Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling a failed client authentication - * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration - */ - public OAuth2ClientAuthenticationConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) - : authorizationServerSettings.getTokenEndpoint(); - String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) - : authorizationServerSettings.getTokenIntrospectionEndpoint(); - String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) - : authorizationServerSettings.getTokenRevocationEndpoint(); - String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) - : authorizationServerSettings.getDeviceAuthorizationEndpoint(); - String pushedAuthorizationRequestEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) - : authorizationServerSettings.getPushedAuthorizationRequestEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, tokenEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, tokenIntrospectionEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, tokenRevocationEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, deviceAuthorizationEndpointUri), - PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, pushedAuthorizationRequestEndpointUri)); - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter( - authenticationManager, this.requestMatcher); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.authenticationConverters.isEmpty()) { - authenticationConverters.addAll(0, this.authenticationConverters); - } - this.authenticationConvertersConsumer.accept(authenticationConverters); - clientAuthenticationFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.authenticationSuccessHandler != null) { - clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler); - } - if (this.errorResponseHandler != null) { - clientAuthenticationFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(clientAuthenticationFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new JwtClientAssertionAuthenticationConverter()); - authenticationConverters.add(new ClientSecretBasicAuthenticationConverter()); - authenticationConverters.add(new ClientSecretPostAuthenticationConverter()); - authenticationConverters.add(new PublicClientAuthenticationConverter()); - authenticationConverters.add(new X509ClientCertificateAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils - .getRegisteredClientRepository(httpSecurity); - OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity); - - JwtClientAssertionAuthenticationProvider jwtClientAssertionAuthenticationProvider = new JwtClientAssertionAuthenticationProvider( - registeredClientRepository, authorizationService); - authenticationProviders.add(jwtClientAssertionAuthenticationProvider); - - X509ClientCertificateAuthenticationProvider x509ClientCertificateAuthenticationProvider = new X509ClientCertificateAuthenticationProvider( - registeredClientRepository, authorizationService); - authenticationProviders.add(x509ClientCertificateAuthenticationProvider); - - ClientSecretAuthenticationProvider clientSecretAuthenticationProvider = new ClientSecretAuthenticationProvider( - registeredClientRepository, authorizationService); - PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, PasswordEncoder.class); - if (passwordEncoder != null) { - clientSecretAuthenticationProvider.setPasswordEncoder(passwordEncoder); - } - authenticationProviders.add(clientSecretAuthenticationProvider); - - PublicClientAuthenticationProvider publicClientAuthenticationProvider = new PublicClientAuthenticationProvider( - registeredClientRepository, authorizationService); - authenticationProviders.add(publicClientAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java deleted file mode 100644 index dd9bf859b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.Map; - -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; - -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.core.ResolvableType; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Utility methods for the OAuth 2.0 Configurers. - * - * @author Joe Grandja - * @since 0.1.2 - */ -final class OAuth2ConfigurerUtils { - - private OAuth2ConfigurerUtils() { - } - - static String withMultipleIssuersPattern(String endpointUri) { - Assert.hasText(endpointUri, "endpointUri cannot be empty"); - return endpointUri.startsWith("/") ? "/**" + endpointUri : "/**/" + endpointUri; - } - - static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) { - RegisteredClientRepository registeredClientRepository = httpSecurity - .getSharedObject(RegisteredClientRepository.class); - if (registeredClientRepository == null) { - registeredClientRepository = getBean(httpSecurity, RegisteredClientRepository.class); - httpSecurity.setSharedObject(RegisteredClientRepository.class, registeredClientRepository); - } - return registeredClientRepository; - } - - static OAuth2AuthorizationService getAuthorizationService(HttpSecurity httpSecurity) { - OAuth2AuthorizationService authorizationService = httpSecurity - .getSharedObject(OAuth2AuthorizationService.class); - if (authorizationService == null) { - authorizationService = getOptionalBean(httpSecurity, OAuth2AuthorizationService.class); - if (authorizationService == null) { - authorizationService = new InMemoryOAuth2AuthorizationService(); - } - httpSecurity.setSharedObject(OAuth2AuthorizationService.class, authorizationService); - } - return authorizationService; - } - - static OAuth2AuthorizationConsentService getAuthorizationConsentService(HttpSecurity httpSecurity) { - OAuth2AuthorizationConsentService authorizationConsentService = httpSecurity - .getSharedObject(OAuth2AuthorizationConsentService.class); - if (authorizationConsentService == null) { - authorizationConsentService = getOptionalBean(httpSecurity, OAuth2AuthorizationConsentService.class); - if (authorizationConsentService == null) { - authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService(); - } - httpSecurity.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService); - } - return authorizationConsentService; - } - - @SuppressWarnings("unchecked") - static OAuth2TokenGenerator getTokenGenerator(HttpSecurity httpSecurity) { - OAuth2TokenGenerator tokenGenerator = httpSecurity - .getSharedObject(OAuth2TokenGenerator.class); - if (tokenGenerator == null) { - tokenGenerator = getOptionalBean(httpSecurity, OAuth2TokenGenerator.class); - if (tokenGenerator == null) { - JwtGenerator jwtGenerator = getJwtGenerator(httpSecurity); - OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); - accessTokenGenerator.setAccessTokenCustomizer(getAccessTokenCustomizer(httpSecurity)); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - if (jwtGenerator != null) { - tokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, - refreshTokenGenerator); - } - else { - tokenGenerator = new DelegatingOAuth2TokenGenerator(accessTokenGenerator, refreshTokenGenerator); - } - } - httpSecurity.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator); - } - return tokenGenerator; - } - - private static JwtGenerator getJwtGenerator(HttpSecurity httpSecurity) { - JwtGenerator jwtGenerator = httpSecurity.getSharedObject(JwtGenerator.class); - if (jwtGenerator == null) { - JwtEncoder jwtEncoder = getJwtEncoder(httpSecurity); - if (jwtEncoder != null) { - jwtGenerator = new JwtGenerator(jwtEncoder); - jwtGenerator.setJwtCustomizer(getJwtCustomizer(httpSecurity)); - httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator); - } - } - return jwtGenerator; - } - - private static JwtEncoder getJwtEncoder(HttpSecurity httpSecurity) { - JwtEncoder jwtEncoder = httpSecurity.getSharedObject(JwtEncoder.class); - if (jwtEncoder == null) { - jwtEncoder = getOptionalBean(httpSecurity, JwtEncoder.class); - if (jwtEncoder == null) { - JWKSource jwkSource = getJwkSource(httpSecurity); - if (jwkSource != null) { - jwtEncoder = new NimbusJwtEncoder(jwkSource); - } - } - if (jwtEncoder != null) { - httpSecurity.setSharedObject(JwtEncoder.class, jwtEncoder); - } - } - return jwtEncoder; - } - - @SuppressWarnings("unchecked") - static JWKSource getJwkSource(HttpSecurity httpSecurity) { - JWKSource jwkSource = httpSecurity.getSharedObject(JWKSource.class); - if (jwkSource == null) { - ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class); - jwkSource = getOptionalBean(httpSecurity, type); - if (jwkSource != null) { - httpSecurity.setSharedObject(JWKSource.class, jwkSource); - } - } - return jwkSource; - } - - private static OAuth2TokenCustomizer getJwtCustomizer(HttpSecurity httpSecurity) { - final OAuth2TokenCustomizer defaultJwtCustomizer = DefaultOAuth2TokenCustomizers - .jwtCustomizer(); - ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, - JwtEncodingContext.class); - final OAuth2TokenCustomizer jwtCustomizer = getOptionalBean(httpSecurity, type); - if (jwtCustomizer == null) { - return defaultJwtCustomizer; - } - return (context) -> { - defaultJwtCustomizer.customize(context); - jwtCustomizer.customize(context); - }; - } - - private static OAuth2TokenCustomizer getAccessTokenCustomizer(HttpSecurity httpSecurity) { - final OAuth2TokenCustomizer defaultAccessTokenCustomizer = DefaultOAuth2TokenCustomizers - .accessTokenCustomizer(); - ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, - OAuth2TokenClaimsContext.class); - OAuth2TokenCustomizer accessTokenCustomizer = getOptionalBean(httpSecurity, type); - if (accessTokenCustomizer == null) { - return defaultAccessTokenCustomizer; - } - return (context) -> { - defaultAccessTokenCustomizer.customize(context); - accessTokenCustomizer.customize(context); - }; - } - - static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = httpSecurity - .getSharedObject(AuthorizationServerSettings.class); - if (authorizationServerSettings == null) { - authorizationServerSettings = getBean(httpSecurity, AuthorizationServerSettings.class); - httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings); - } - return authorizationServerSettings; - } - - static T getBean(HttpSecurity httpSecurity, Class type) { - return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type); - } - - @SuppressWarnings("unchecked") - static T getBean(HttpSecurity httpSecurity, ResolvableType type) { - ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); - String[] names = context.getBeanNamesForType(type); - if (names.length == 1) { - return (T) context.getBean(names[0]); - } - if (names.length > 1) { - throw new NoUniqueBeanDefinitionException(type, names); - } - throw new NoSuchBeanDefinitionException(type); - } - - static T getOptionalBean(HttpSecurity httpSecurity, Class type) { - Map beansMap = BeanFactoryUtils - .beansOfTypeIncludingAncestors(httpSecurity.getSharedObject(ApplicationContext.class), type); - if (beansMap.size() > 1) { - throw new NoUniqueBeanDefinitionException(type, beansMap.size(), - "Expected single matching bean of type '" + type.getName() + "' but found " + beansMap.size() + ": " - + StringUtils.collectionToCommaDelimitedString(beansMap.keySet())); - } - return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null); - } - - @SuppressWarnings("unchecked") - static T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { - ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); - String[] names = context.getBeanNamesForType(type); - if (names.length > 1) { - throw new NoUniqueBeanDefinitionException(type, names); - } - return (names.length == 1) ? (T) context.getBean(names[0]) : null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java deleted file mode 100644 index 5ef70885e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceAuthorizationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationRequestAuthenticationConverter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Configurer for the OAuth 2.0 Device Authorization Endpoint. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2AuthorizationServerConfigurer#deviceAuthorizationEndpoint - * @see OAuth2DeviceAuthorizationEndpointFilter - */ -public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List deviceAuthorizationRequestConverters = new ArrayList<>(); - - private Consumer> deviceAuthorizationRequestConvertersConsumer = ( - deviceAuthorizationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler deviceAuthorizationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - private String verificationUri; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2DeviceAuthorizationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Device - * Authorization Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} used for authenticating - * the request. - * @param deviceAuthorizationRequestConverter the {@link AuthenticationConverter} used - * when attempting to extract a Device Authorization Request from - * {@link HttpServletRequest} - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationRequestConverter( - AuthenticationConverter deviceAuthorizationRequestConverter) { - Assert.notNull(deviceAuthorizationRequestConverter, "deviceAuthorizationRequestConverter cannot be null"); - this.deviceAuthorizationRequestConverters.add(deviceAuthorizationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added - * {@link #deviceAuthorizationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param deviceAuthorizationRequestConvertersConsumer the {@code Consumer} providing - * access to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationRequestConverters( - Consumer> deviceAuthorizationRequestConvertersConsumer) { - Assert.notNull(deviceAuthorizationRequestConvertersConsumer, - "deviceAuthorizationRequestConvertersConsumer cannot be null"); - this.deviceAuthorizationRequestConvertersConsumer = deviceAuthorizationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} and returning the - * {@link OAuth2DeviceAuthorizationResponse Device Authorization Response}. - * @param deviceAuthorizationResponseHandler the {@link AuthenticationSuccessHandler} - * used for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationResponseHandler( - AuthenticationSuccessHandler deviceAuthorizationResponseHandler) { - this.deviceAuthorizationResponseHandler = deviceAuthorizationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - /** - * Sets the end-user verification {@code URI} on the authorization server. - * @param verificationUri the end-user verification {@code URI} on the authorization - * server - * @return the {@link OAuth2DeviceAuthorizationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceAuthorizationEndpointConfigurer verificationUri(String verificationUri) { - this.verificationUri = verificationUri; - return this; - } - - @Override - public void init(HttpSecurity builder) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(builder); - String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) - : authorizationServerSettings.getDeviceAuthorizationEndpoint(); - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, deviceAuthorizationEndpointUri); - - List authenticationProviders = createDefaultAuthenticationProviders(builder); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders - .forEach((authenticationProvider) -> builder.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - public void configure(HttpSecurity builder) { - AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(builder); - - String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) - : authorizationServerSettings.getDeviceAuthorizationEndpoint(); - OAuth2DeviceAuthorizationEndpointFilter deviceAuthorizationEndpointFilter = new OAuth2DeviceAuthorizationEndpointFilter( - authenticationManager, deviceAuthorizationEndpointUri); - - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.deviceAuthorizationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.deviceAuthorizationRequestConverters); - } - this.deviceAuthorizationRequestConvertersConsumer.accept(authenticationConverters); - deviceAuthorizationEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.deviceAuthorizationResponseHandler != null) { - deviceAuthorizationEndpointFilter.setAuthenticationSuccessHandler(this.deviceAuthorizationResponseHandler); - } - if (this.errorResponseHandler != null) { - deviceAuthorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - if (StringUtils.hasText(this.verificationUri)) { - deviceAuthorizationEndpointFilter.setVerificationUri(this.verificationUri); - } - builder.addFilterAfter(postProcess(deviceAuthorizationEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - authenticationConverters.add(new OAuth2DeviceAuthorizationRequestAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity builder) { - List authenticationProviders = new ArrayList<>(); - - OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder); - - OAuth2DeviceAuthorizationRequestAuthenticationProvider deviceAuthorizationRequestAuthenticationProvider = new OAuth2DeviceAuthorizationRequestAuthenticationProvider( - authorizationService); - authenticationProviders.add(deviceAuthorizationRequestAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java deleted file mode 100644 index 4447af271..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Configurer for the OAuth 2.0 Device Verification Endpoint. - * - * @author Steve Riesenberg - * @since 1.1 - * @see OAuth2AuthorizationServerConfigurer#deviceVerificationEndpoint - * @see OAuth2DeviceVerificationEndpointFilter - */ -public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List deviceVerificationRequestConverters = new ArrayList<>(); - - private Consumer> deviceVerificationRequestConvertersConsumer = ( - deviceVerificationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler deviceVerificationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - private String consentPage; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2DeviceVerificationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Device - * Verification Request (or Device Authorization Consent) from - * {@link HttpServletRequest} to an instance of - * {@link OAuth2DeviceVerificationAuthenticationToken} or - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} used for authenticating - * the request. - * @param deviceVerificationRequestConverter the {@link AuthenticationConverter} used - * when attempting to extract a Device Verification Request (or Device Authorization - * Consent) from {@link HttpServletRequest} - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationRequestConverter( - AuthenticationConverter deviceVerificationRequestConverter) { - Assert.notNull(deviceVerificationRequestConverter, "deviceVerificationRequestConverter cannot be null"); - this.deviceVerificationRequestConverters.add(deviceVerificationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added - * {@link #deviceVerificationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param deviceVerificationRequestConvertersConsumer the {@code Consumer} providing - * access to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationRequestConverters( - Consumer> deviceVerificationRequestConvertersConsumer) { - Assert.notNull(deviceVerificationRequestConvertersConsumer, - "deviceVerificationRequestConvertersConsumer cannot be null"); - this.deviceVerificationRequestConvertersConsumer = deviceVerificationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OAuth2DeviceVerificationAuthenticationToken} or - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OAuth2DeviceVerificationAuthenticationToken} or - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2DeviceVerificationAuthenticationToken} and returning the response. - * @param deviceVerificationResponseHandler the {@link AuthenticationSuccessHandler} - * used for handling an {@link OAuth2DeviceVerificationAuthenticationToken} - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer deviceVerificationResponseHandler( - AuthenticationSuccessHandler deviceVerificationResponseHandler) { - this.deviceVerificationResponseHandler = deviceVerificationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - /** - * Specify the URI to redirect Resource Owners to if consent is required during the - * {@code device_code} flow. A default consent page will be generated when this - * attribute is not specified. - * - * If a URI is specified, applications are required to process the specified URI to - * generate a consent page. The query string will contain the following parameters: - * - *
    - *
  • {@code client_id} - the client identifier
  • - *
  • {@code scope} - a space-delimited list of scopes present in the device - * authorization request
  • - *
  • {@code state} - a CSRF protection token
  • - *
  • {@code user_code} - the user code
  • - *
- * - * In general, the consent page should create a form that submits a request with the - * following requirements: - * - *
    - *
  • It must be an HTTP POST
  • - *
  • It must be submitted to - * {@link AuthorizationServerSettings#getDeviceVerificationEndpoint()}
  • - *
  • It must include the received {@code client_id} as an HTTP parameter
  • - *
  • It must include the received {@code state} as an HTTP parameter
  • - *
  • It must include the list of {@code scope}s the {@code Resource Owner} consented - * to as an HTTP parameter
  • - *
  • It must include the received {@code user_code} as an HTTP parameter
  • - *
- * @param consentPage the URI of the custom consent page to redirect to if consent is - * required (e.g. "/oauth2/consent") - * @return the {@link OAuth2DeviceVerificationEndpointConfigurer} for further - * configuration - */ - public OAuth2DeviceVerificationEndpointConfigurer consentPage(String consentPage) { - this.consentPage = consentPage; - return this; - } - - @Override - public void init(HttpSecurity builder) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(builder); - String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) - : authorizationServerSettings.getDeviceVerificationEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, deviceVerificationEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, deviceVerificationEndpointUri)); - - List authenticationProviders = createDefaultAuthenticationProviders(builder); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders - .forEach((authenticationProvider) -> builder.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - public void configure(HttpSecurity builder) { - AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(builder); - - String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) - : authorizationServerSettings.getDeviceVerificationEndpoint(); - OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter = new OAuth2DeviceVerificationEndpointFilter( - authenticationManager, deviceVerificationEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.deviceVerificationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.deviceVerificationRequestConverters); - } - this.deviceVerificationRequestConvertersConsumer.accept(authenticationConverters); - deviceVerificationEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.deviceVerificationResponseHandler != null) { - deviceVerificationEndpointFilter.setAuthenticationSuccessHandler(this.deviceVerificationResponseHandler); - } - if (this.errorResponseHandler != null) { - deviceVerificationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - if (StringUtils.hasText(this.consentPage)) { - deviceVerificationEndpointFilter.setConsentPage(this.consentPage); - } - builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2DeviceVerificationAuthenticationConverter()); - authenticationConverters.add(new OAuth2DeviceAuthorizationConsentAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity builder) { - RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils - .getRegisteredClientRepository(builder); - OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder); - OAuth2AuthorizationConsentService authorizationConsentService = OAuth2ConfigurerUtils - .getAuthorizationConsentService(builder); - - List authenticationProviders = new ArrayList<>(); - - // @formatter:off - OAuth2DeviceVerificationAuthenticationProvider deviceVerificationAuthenticationProvider = - new OAuth2DeviceVerificationAuthenticationProvider( - registeredClientRepository, authorizationService, authorizationConsentService); - // @formatter:on - authenticationProviders.add(deviceVerificationAuthenticationProvider); - - // @formatter:off - OAuth2DeviceAuthorizationConsentAuthenticationProvider deviceAuthorizationConsentAuthenticationProvider = - new OAuth2DeviceAuthorizationConsentAuthenticationProvider( - registeredClientRepository, authorizationService, authorizationConsentService); - // @formatter:on - authenticationProviders.add(deviceAuthorizationConsentAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java deleted file mode 100644 index f71c0f747..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2PushedAuthorizationRequestEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for the OAuth 2.0 Pushed Authorization Request Endpoint. - * - * @author Joe Grandja - * @since 1.5 - * @see OAuth2AuthorizationServerConfigurer#pushedAuthorizationRequestEndpoint - * @see OAuth2PushedAuthorizationRequestEndpointFilter - */ -public final class OAuth2PushedAuthorizationRequestEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List pushedAuthorizationRequestConverters = new ArrayList<>(); - - private Consumer> pushedAuthorizationRequestConvertersConsumer = ( - authorizationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler pushedAuthorizationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - private Consumer authorizationCodeRequestAuthenticationValidator; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2PushedAuthorizationRequestEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract a Pushed - * Authorization Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken} used for authenticating - * the request. - * @param pushedAuthorizationRequestConverter an {@link AuthenticationConverter} used - * when attempting to extract a Pushed Authorization Request from - * {@link HttpServletRequest} - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestConverter( - AuthenticationConverter pushedAuthorizationRequestConverter) { - Assert.notNull(pushedAuthorizationRequestConverter, "pushedAuthorizationRequestConverter cannot be null"); - this.pushedAuthorizationRequestConverters.add(pushedAuthorizationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added - * {@link #pushedAuthorizationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param pushedAuthorizationRequestConvertersConsumer the {@code Consumer} providing - * access to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestConverters( - Consumer> pushedAuthorizationRequestConvertersConsumer) { - Assert.notNull(pushedAuthorizationRequestConvertersConsumer, - "pushedAuthorizationRequestConvertersConsumer cannot be null"); - this.pushedAuthorizationRequestConvertersConsumer = pushedAuthorizationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OAuth2PushedAuthorizationRequestAuthenticationToken} - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken} and returning the - * Pushed Authorization Response. - * @param pushedAuthorizationResponseHandler the {@link AuthenticationSuccessHandler} - * used for handling an {@link OAuth2PushedAuthorizationRequestAuthenticationToken} - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationResponseHandler( - AuthenticationSuccessHandler pushedAuthorizationResponseHandler) { - this.pushedAuthorizationResponseHandler = pushedAuthorizationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationException} and returning the - * {@link OAuth2Error Error Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException} - * @return the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer} for further - * configuration - */ - public OAuth2PushedAuthorizationRequestEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - void addAuthorizationCodeRequestAuthenticationValidator( - Consumer authenticationValidator) { - this.authorizationCodeRequestAuthenticationValidator = (this.authorizationCodeRequestAuthenticationValidator == null) - ? authenticationValidator - : this.authorizationCodeRequestAuthenticationValidator.andThen(authenticationValidator); - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String pushedAuthorizationRequestEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) - : authorizationServerSettings.getPushedAuthorizationRequestEndpoint(); - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, pushedAuthorizationRequestEndpointUri); - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String pushedAuthorizationRequestEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) - : authorizationServerSettings.getPushedAuthorizationRequestEndpoint(); - OAuth2PushedAuthorizationRequestEndpointFilter pushedAuthorizationRequestEndpointFilter = new OAuth2PushedAuthorizationRequestEndpointFilter( - authenticationManager, pushedAuthorizationRequestEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.pushedAuthorizationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.pushedAuthorizationRequestConverters); - } - this.pushedAuthorizationRequestConvertersConsumer.accept(authenticationConverters); - pushedAuthorizationRequestEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.pushedAuthorizationResponseHandler != null) { - pushedAuthorizationRequestEndpointFilter - .setAuthenticationSuccessHandler(this.pushedAuthorizationResponseHandler); - } - if (this.errorResponseHandler != null) { - pushedAuthorizationRequestEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(pushedAuthorizationRequestEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2AuthorizationCodeRequestAuthenticationConverter()); - - return authenticationConverters; - } - - private List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OAuth2PushedAuthorizationRequestAuthenticationProvider pushedAuthorizationRequestAuthenticationProvider = new OAuth2PushedAuthorizationRequestAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity)); - if (this.authorizationCodeRequestAuthenticationValidator != null) { - pushedAuthorizationRequestAuthenticationProvider - .setAuthenticationValidator(new OAuth2AuthorizationCodeRequestAuthenticationValidator() - .andThen(this.authorizationCodeRequestAuthenticationValidator)); - } - authenticationProviders.add(pushedAuthorizationRequestAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java deleted file mode 100644 index beefb48ee..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenExchangeAuthenticationConverter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for the OAuth 2.0 Token Endpoint. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationServerConfigurer#tokenEndpoint - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List accessTokenRequestConverters = new ArrayList<>(); - - private Consumer> accessTokenRequestConvertersConsumer = ( - accessTokenRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler accessTokenResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2TokenEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract an Access - * Token Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the - * authorization grant. - * @param accessTokenRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract an Access Token Request from {@link HttpServletRequest} - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - */ - public OAuth2TokenEndpointConfigurer accessTokenRequestConverter( - AuthenticationConverter accessTokenRequestConverter) { - Assert.notNull(accessTokenRequestConverter, "accessTokenRequestConverter cannot be null"); - this.accessTokenRequestConverters.add(accessTokenRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #accessTokenRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param accessTokenRequestConvertersConsumer the {@code Consumer} providing access - * to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2TokenEndpointConfigurer accessTokenRequestConverters( - Consumer> accessTokenRequestConvertersConsumer) { - Assert.notNull(accessTokenRequestConvertersConsumer, "accessTokenRequestConvertersConsumer cannot be null"); - this.accessTokenRequestConvertersConsumer = accessTokenRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating a type of - * {@link OAuth2AuthorizationGrantAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken} - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - */ - public OAuth2TokenEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OAuth2TokenEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2AccessTokenAuthenticationToken} and returning the - * {@link OAuth2AccessTokenResponse Access Token Response}. - * @param accessTokenResponseHandler the {@link AuthenticationSuccessHandler} used for - * handling an {@link OAuth2AccessTokenAuthenticationToken} - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - */ - public OAuth2TokenEndpointConfigurer accessTokenResponseHandler( - AuthenticationSuccessHandler accessTokenResponseHandler) { - this.accessTokenResponseHandler = accessTokenResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration - */ - public OAuth2TokenEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) - : authorizationServerSettings.getTokenEndpoint(); - this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, tokenEndpointUri); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) - : authorizationServerSettings.getTokenEndpoint(); - OAuth2TokenEndpointFilter tokenEndpointFilter = new OAuth2TokenEndpointFilter(authenticationManager, - tokenEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.accessTokenRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.accessTokenRequestConverters); - } - this.accessTokenRequestConvertersConsumer.accept(authenticationConverters); - tokenEndpointFilter.setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.accessTokenResponseHandler != null) { - tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler); - } - if (this.errorResponseHandler != null) { - tokenEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(tokenEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2AuthorizationCodeAuthenticationConverter()); - authenticationConverters.add(new OAuth2RefreshTokenAuthenticationConverter()); - authenticationConverters.add(new OAuth2ClientCredentialsAuthenticationConverter()); - authenticationConverters.add(new OAuth2DeviceCodeAuthenticationConverter()); - authenticationConverters.add(new OAuth2TokenExchangeAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity); - OAuth2TokenGenerator tokenGenerator = OAuth2ConfigurerUtils - .getTokenGenerator(httpSecurity); - - OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider( - authorizationService, tokenGenerator); - SessionRegistry sessionRegistry = httpSecurity.getSharedObject(SessionRegistry.class); - if (sessionRegistry != null) { - authorizationCodeAuthenticationProvider.setSessionRegistry(sessionRegistry); - } - authenticationProviders.add(authorizationCodeAuthenticationProvider); - - OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider = new OAuth2RefreshTokenAuthenticationProvider( - authorizationService, tokenGenerator); - authenticationProviders.add(refreshTokenAuthenticationProvider); - - OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider( - authorizationService, tokenGenerator); - authenticationProviders.add(clientCredentialsAuthenticationProvider); - - OAuth2DeviceCodeAuthenticationProvider deviceCodeAuthenticationProvider = new OAuth2DeviceCodeAuthenticationProvider( - authorizationService, tokenGenerator); - authenticationProviders.add(deviceCodeAuthenticationProvider); - - OAuth2TokenExchangeAuthenticationProvider tokenExchangeAuthenticationProvider = new OAuth2TokenExchangeAuthenticationProvider( - authorizationService, tokenGenerator); - authenticationProviders.add(tokenExchangeAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java deleted file mode 100644 index 300a85d36..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for the OAuth 2.0 Token Introspection Endpoint. - * - * @author Gaurav Tiwari - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2AuthorizationServerConfigurer#tokenIntrospectionEndpoint(Customizer) - * @see OAuth2TokenIntrospectionEndpointFilter - */ -public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List introspectionRequestConverters = new ArrayList<>(); - - private Consumer> introspectionRequestConvertersConsumer = ( - introspectionRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler introspectionResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2TokenIntrospectionEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract an - * Introspection Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the - * request. - * @param introspectionRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract an Introspection Request from {@link HttpServletRequest} - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverter( - AuthenticationConverter introspectionRequestConverter) { - Assert.notNull(introspectionRequestConverter, "introspectionRequestConverter cannot be null"); - this.introspectionRequestConverters.add(introspectionRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #introspectionRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param introspectionRequestConvertersConsumer the {@code Consumer} providing access - * to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverters( - Consumer> introspectionRequestConvertersConsumer) { - Assert.notNull(introspectionRequestConvertersConsumer, "introspectionRequestConvertersConsumer cannot be null"); - this.introspectionRequestConvertersConsumer = introspectionRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating a type of - * {@link OAuth2TokenIntrospectionAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken} - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenIntrospectionEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OAuth2TokenIntrospectionEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2TokenIntrospectionAuthenticationToken}. - * @param introspectionResponseHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2TokenIntrospectionAuthenticationToken} - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenIntrospectionEndpointConfigurer introspectionResponseHandler( - AuthenticationSuccessHandler introspectionResponseHandler) { - this.introspectionResponseHandler = introspectionResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenIntrospectionEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) - : authorizationServerSettings.getTokenIntrospectionEndpoint(); - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, tokenIntrospectionEndpointUri); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) - : authorizationServerSettings.getTokenIntrospectionEndpoint(); - OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter = new OAuth2TokenIntrospectionEndpointFilter( - authenticationManager, tokenIntrospectionEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.introspectionRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.introspectionRequestConverters); - } - this.introspectionRequestConvertersConsumer.accept(authenticationConverters); - introspectionEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.introspectionResponseHandler != null) { - introspectionEndpointFilter.setAuthenticationSuccessHandler(this.introspectionResponseHandler); - } - if (this.errorResponseHandler != null) { - introspectionEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(introspectionEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2TokenIntrospectionAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider = new OAuth2TokenIntrospectionAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity)); - authenticationProviders.add(tokenIntrospectionAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java deleted file mode 100644 index e26499e67..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for the OAuth 2.0 Token Revocation Endpoint. - * - * @author Arfat Chaus - * @author Joe Grandja - * @since 0.2.2 - * @see OAuth2AuthorizationServerConfigurer#tokenRevocationEndpoint - * @see OAuth2TokenRevocationEndpointFilter - */ -public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List revocationRequestConverters = new ArrayList<>(); - - private Consumer> revocationRequestConvertersConsumer = ( - revocationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler revocationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OAuth2TokenRevocationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract a Revoke - * Token Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the - * request. - * @param revocationRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract a Revoke Token Request from {@link HttpServletRequest} - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverter( - AuthenticationConverter revocationRequestConverter) { - Assert.notNull(revocationRequestConverter, "revocationRequestConverter cannot be null"); - this.revocationRequestConverters.add(revocationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #revocationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param revocationRequestConvertersConsumer the {@code Consumer} providing access to - * the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverters( - Consumer> revocationRequestConvertersConsumer) { - Assert.notNull(revocationRequestConvertersConsumer, "revocationRequestConvertersConsumer cannot be null"); - this.revocationRequestConvertersConsumer = revocationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating a type of - * {@link OAuth2TokenRevocationAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating a type of {@link OAuth2TokenRevocationAuthenticationToken} - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenRevocationEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OAuth2TokenRevocationEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2TokenRevocationAuthenticationToken}. - * @param revocationResponseHandler the {@link AuthenticationSuccessHandler} used for - * handling an {@link OAuth2TokenRevocationAuthenticationToken} - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenRevocationEndpointConfigurer revocationResponseHandler( - AuthenticationSuccessHandler revocationResponseHandler) { - this.revocationResponseHandler = revocationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further - * configuration - */ - public OAuth2TokenRevocationEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) - : authorizationServerSettings.getTokenRevocationEndpoint(); - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, tokenRevocationEndpointUri); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) - : authorizationServerSettings.getTokenRevocationEndpoint(); - OAuth2TokenRevocationEndpointFilter revocationEndpointFilter = new OAuth2TokenRevocationEndpointFilter( - authenticationManager, tokenRevocationEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.revocationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.revocationRequestConverters); - } - this.revocationRequestConvertersConsumer.accept(authenticationConverters); - revocationEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.revocationResponseHandler != null) { - revocationEndpointFilter.setAuthenticationSuccessHandler(this.revocationResponseHandler); - } - if (this.errorResponseHandler != null) { - revocationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(revocationEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OAuth2TokenRevocationAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider = new OAuth2TokenRevocationAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity)); - authenticationProviders.add(tokenRevocationAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java deleted file mode 100644 index 57be2bfed..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for OpenID Connect 1.0 Dynamic Client Registration Endpoint. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @since 0.2.0 - * @see OidcConfigurer#clientRegistrationEndpoint - * @see OidcClientRegistrationEndpointFilter - */ -public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List clientRegistrationRequestConverters = new ArrayList<>(); - - private Consumer> clientRegistrationRequestConvertersConsumer = ( - clientRegistrationRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler clientRegistrationResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OidcClientRegistrationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract a Client - * Registration Request from {@link HttpServletRequest} to an instance of - * {@link OidcClientRegistrationAuthenticationToken} used for authenticating the - * request. - * @param clientRegistrationRequestConverter an {@link AuthenticationConverter} used - * when attempting to extract a Client Registration Request from - * {@link HttpServletRequest} - * @return the {@link OidcClientRegistrationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverter( - AuthenticationConverter clientRegistrationRequestConverter) { - Assert.notNull(clientRegistrationRequestConverter, "clientRegistrationRequestConverter cannot be null"); - this.clientRegistrationRequestConverters.add(clientRegistrationRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added - * {@link #clientRegistrationRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param clientRegistrationRequestConvertersConsumer the {@code Consumer} providing - * access to the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverters( - Consumer> clientRegistrationRequestConvertersConsumer) { - Assert.notNull(clientRegistrationRequestConvertersConsumer, - "clientRegistrationRequestConvertersConsumer cannot be null"); - this.clientRegistrationRequestConvertersConsumer = clientRegistrationRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OidcClientRegistrationAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OidcClientRegistrationAuthenticationToken} - * @return the {@link OidcClientRegistrationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer authenticationProvider( - AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OidcClientRegistrationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcClientRegistrationAuthenticationToken} and returning the - * {@link OidcClientRegistration Client Registration Response}. - * @param clientRegistrationResponseHandler the {@link AuthenticationSuccessHandler} - * used for handling an {@link OidcClientRegistrationAuthenticationToken} - * @return the {@link OidcClientRegistrationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer clientRegistrationResponseHandler( - AuthenticationSuccessHandler clientRegistrationResponseHandler) { - this.clientRegistrationResponseHandler = clientRegistrationResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OidcClientRegistrationEndpointConfigurer} for further - * configuration - * @since 0.4.0 - */ - public OidcClientRegistrationEndpointConfigurer errorResponseHandler( - AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) - : authorizationServerSettings.getOidcClientRegistrationEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, clientRegistrationEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, clientRegistrationEndpointUri)); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) - : authorizationServerSettings.getOidcClientRegistrationEndpoint(); - OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter = new OidcClientRegistrationEndpointFilter( - authenticationManager, clientRegistrationEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.clientRegistrationRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.clientRegistrationRequestConverters); - } - this.clientRegistrationRequestConvertersConsumer.accept(authenticationConverters); - oidcClientRegistrationEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.clientRegistrationResponseHandler != null) { - oidcClientRegistrationEndpointFilter - .setAuthenticationSuccessHandler(this.clientRegistrationResponseHandler); - } - if (this.errorResponseHandler != null) { - oidcClientRegistrationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OidcClientRegistrationAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider = new OidcClientRegistrationAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity), - OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity)); - PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, PasswordEncoder.class); - if (passwordEncoder != null) { - oidcClientRegistrationAuthenticationProvider.setPasswordEncoder(passwordEncoder); - } - authenticationProviders.add(oidcClientRegistrationAuthenticationProvider); - - OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider = new OidcClientConfigurationAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity)); - authenticationProviders.add(oidcClientConfigurationAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java deleted file mode 100644 index 692811b88..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * Configurer for OpenID Connect 1.0 support. - * - * @author Joe Grandja - * @since 0.2.0 - * @see OAuth2AuthorizationServerConfigurer#oidc - * @see OidcProviderConfigurationEndpointConfigurer - * @see OidcLogoutEndpointConfigurer - * @see OidcClientRegistrationEndpointConfigurer - * @see OidcUserInfoEndpointConfigurer - */ -public final class OidcConfigurer extends AbstractOAuth2Configurer { - - private final Map, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>(); - - private RequestMatcher requestMatcher; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OidcConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - addConfigurer(OidcProviderConfigurationEndpointConfigurer.class, - new OidcProviderConfigurationEndpointConfigurer(objectPostProcessor)); - addConfigurer(OidcLogoutEndpointConfigurer.class, new OidcLogoutEndpointConfigurer(objectPostProcessor)); - addConfigurer(OidcUserInfoEndpointConfigurer.class, new OidcUserInfoEndpointConfigurer(objectPostProcessor)); - } - - /** - * Configures the OpenID Connect 1.0 Provider Configuration Endpoint. - * @param providerConfigurationEndpointCustomizer the {@link Customizer} providing - * access to the {@link OidcProviderConfigurationEndpointConfigurer} - * @return the {@link OidcConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcConfigurer providerConfigurationEndpoint( - Customizer providerConfigurationEndpointCustomizer) { - providerConfigurationEndpointCustomizer - .customize(getConfigurer(OidcProviderConfigurationEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OpenID Connect 1.0 RP-Initiated Logout Endpoint. - * @param logoutEndpointCustomizer the {@link Customizer} providing access to the - * {@link OidcLogoutEndpointConfigurer} - * @return the {@link OidcConfigurer} for further configuration - * @since 1.1 - */ - public OidcConfigurer logoutEndpoint(Customizer logoutEndpointCustomizer) { - logoutEndpointCustomizer.customize(getConfigurer(OidcLogoutEndpointConfigurer.class)); - return this; - } - - /** - * Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint. - * @param clientRegistrationEndpointCustomizer the {@link Customizer} providing access - * to the {@link OidcClientRegistrationEndpointConfigurer} - * @return the {@link OidcConfigurer} for further configuration - */ - public OidcConfigurer clientRegistrationEndpoint( - Customizer clientRegistrationEndpointCustomizer) { - OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer = getConfigurer( - OidcClientRegistrationEndpointConfigurer.class); - if (clientRegistrationEndpointConfigurer == null) { - addConfigurer(OidcClientRegistrationEndpointConfigurer.class, - new OidcClientRegistrationEndpointConfigurer(getObjectPostProcessor())); - clientRegistrationEndpointConfigurer = getConfigurer(OidcClientRegistrationEndpointConfigurer.class); - } - clientRegistrationEndpointCustomizer.customize(clientRegistrationEndpointConfigurer); - return this; - } - - /** - * Configures the OpenID Connect 1.0 UserInfo Endpoint. - * @param userInfoEndpointCustomizer the {@link Customizer} providing access to the - * {@link OidcUserInfoEndpointConfigurer} - * @return the {@link OidcConfigurer} for further configuration - */ - public OidcConfigurer userInfoEndpoint(Customizer userInfoEndpointCustomizer) { - userInfoEndpointCustomizer.customize(getConfigurer(OidcUserInfoEndpointConfigurer.class)); - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - List requestMatchers = new ArrayList<>(); - this.configurers.values().forEach((configurer) -> { - configurer.init(httpSecurity); - requestMatchers.add(configurer.getRequestMatcher()); - }); - this.requestMatcher = new OrRequestMatcher(requestMatchers); - } - - @Override - void configure(HttpSecurity httpSecurity) { - OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer = getConfigurer( - OidcClientRegistrationEndpointConfigurer.class); - if (clientRegistrationEndpointConfigurer != null) { - OidcProviderConfigurationEndpointConfigurer providerConfigurationEndpointConfigurer = getConfigurer( - OidcProviderConfigurationEndpointConfigurer.class); - - providerConfigurationEndpointConfigurer.addDefaultProviderConfigurationCustomizer((builder) -> { - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String issuer = authorizationServerContext.getIssuer(); - AuthorizationServerSettings authorizationServerSettings = authorizationServerContext - .getAuthorizationServerSettings(); - - String clientRegistrationEndpoint = UriComponentsBuilder.fromUriString(issuer) - .path(authorizationServerSettings.getOidcClientRegistrationEndpoint()) - .build() - .toUriString(); - - builder.clientRegistrationEndpoint(clientRegistrationEndpoint); - }); - } - - this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity)); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - @SuppressWarnings("unchecked") - T getConfigurer(Class type) { - return (T) this.configurers.get(type); - } - - private void addConfigurer(Class configurerType, T configurer) { - this.configurers.put(configurerType, configurer); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java deleted file mode 100644 index 7a5c07775..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcLogoutAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for OpenID Connect 1.0 RP-Initiated Logout Endpoint. - * - * @author Joe Grandja - * @since 1.1 - * @see OidcConfigurer#logoutEndpoint - * @see OidcLogoutEndpointFilter - */ -public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List logoutRequestConverters = new ArrayList<>(); - - private Consumer> logoutRequestConvertersConsumer = (logoutRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler logoutResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OidcLogoutEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract a Logout - * Request from {@link HttpServletRequest} to an instance of - * {@link OidcLogoutAuthenticationToken} used for authenticating the request. - * @param logoutRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract a Logout Request from {@link HttpServletRequest} - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer logoutRequestConverter(AuthenticationConverter logoutRequestConverter) { - Assert.notNull(logoutRequestConverter, "logoutRequestConverter cannot be null"); - this.logoutRequestConverters.add(logoutRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #logoutRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param logoutRequestConvertersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationConverter}'s - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer logoutRequestConverters( - Consumer> logoutRequestConvertersConsumer) { - Assert.notNull(logoutRequestConvertersConsumer, "logoutRequestConvertersConsumer cannot be null"); - this.logoutRequestConvertersConsumer = logoutRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OidcLogoutAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OidcLogoutAuthenticationToken} - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcLogoutAuthenticationToken} and performing the logout. - * @param logoutResponseHandler the {@link AuthenticationSuccessHandler} used for - * handling an {@link OidcLogoutAuthenticationToken} - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer logoutResponseHandler(AuthenticationSuccessHandler logoutResponseHandler) { - this.logoutResponseHandler = logoutResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OidcLogoutEndpointConfigurer} for further configuration - */ - public OidcLogoutEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) - : authorizationServerSettings.getOidcLogoutEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, logoutEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, logoutEndpointUri)); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) - : authorizationServerSettings.getOidcLogoutEndpoint(); - OidcLogoutEndpointFilter oidcLogoutEndpointFilter = new OidcLogoutEndpointFilter(authenticationManager, - logoutEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.logoutRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.logoutRequestConverters); - } - this.logoutRequestConvertersConsumer.accept(authenticationConverters); - oidcLogoutEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.logoutResponseHandler != null) { - oidcLogoutEndpointFilter.setAuthenticationSuccessHandler(this.logoutResponseHandler); - } - if (this.errorResponseHandler != null) { - oidcLogoutEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterBefore(postProcess(oidcLogoutEndpointFilter), LogoutFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add(new OidcLogoutAuthenticationConverter()); - - return authenticationConverters; - } - - private static List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OidcLogoutAuthenticationProvider oidcLogoutAuthenticationProvider = new OidcLogoutAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity), - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity), - httpSecurity.getSharedObject(SessionRegistry.class)); - authenticationProviders.add(oidcLogoutAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java deleted file mode 100644 index 118824d62..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.function.Consumer; - -import org.springframework.http.HttpMethod; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Configurer for the OpenID Connect 1.0 Provider Configuration Endpoint. - * - * @author Joe Grandja - * @since 0.4.0 - * @see OidcConfigurer#providerConfigurationEndpoint - * @see OidcProviderConfigurationEndpointFilter - */ -public final class OidcProviderConfigurationEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private Consumer providerConfigurationCustomizer; - - private Consumer defaultProviderConfigurationCustomizer; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OidcProviderConfigurationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OidcProviderConfiguration.Builder} allowing the ability to customize the - * claims of the OpenID Provider's configuration. - * @param providerConfigurationCustomizer the {@code Consumer} providing access to the - * {@link OidcProviderConfiguration.Builder} - * @return the {@link OidcProviderConfigurationEndpointConfigurer} for further - * configuration - */ - public OidcProviderConfigurationEndpointConfigurer providerConfigurationCustomizer( - Consumer providerConfigurationCustomizer) { - this.providerConfigurationCustomizer = providerConfigurationCustomizer; - return this; - } - - void addDefaultProviderConfigurationCustomizer( - Consumer defaultProviderConfigurationCustomizer) { - this.defaultProviderConfigurationCustomizer = (this.defaultProviderConfigurationCustomizer == null) - ? defaultProviderConfigurationCustomizer - : this.defaultProviderConfigurationCustomizer.andThen(defaultProviderConfigurationCustomizer); - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String oidcProviderConfigurationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? "/**/.well-known/openid-configuration" : "/.well-known/openid-configuration"; - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, oidcProviderConfigurationEndpointUri); - } - - @Override - void configure(HttpSecurity httpSecurity) { - OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = new OidcProviderConfigurationEndpointFilter(); - Consumer providerConfigurationCustomizer = getProviderConfigurationCustomizer(); - if (providerConfigurationCustomizer != null) { - oidcProviderConfigurationEndpointFilter.setProviderConfigurationCustomizer(providerConfigurationCustomizer); - } - httpSecurity.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), - AbstractPreAuthenticatedProcessingFilter.class); - } - - private Consumer getProviderConfigurationCustomizer() { - Consumer providerConfigurationCustomizer = null; - if (this.defaultProviderConfigurationCustomizer != null || this.providerConfigurationCustomizer != null) { - if (this.defaultProviderConfigurationCustomizer != null) { - providerConfigurationCustomizer = this.defaultProviderConfigurationCustomizer; - } - if (this.providerConfigurationCustomizer != null) { - providerConfigurationCustomizer = (providerConfigurationCustomizer != null) - ? providerConfigurationCustomizer.andThen(this.providerConfigurationCustomizer) - : this.providerConfigurationCustomizer; - } - } - return providerConfigurationCustomizer; - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java deleted file mode 100644 index 279a2d717..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Configurer for OpenID Connect 1.0 UserInfo Endpoint. - * - * @author Steve Riesenberg - * @author Daniel Garnier-Moiroux - * @since 0.2.1 - * @see OidcConfigurer#userInfoEndpoint - * @see OidcUserInfoEndpointFilter - */ -public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer { - - private RequestMatcher requestMatcher; - - private final List userInfoRequestConverters = new ArrayList<>(); - - private Consumer> userInfoRequestConvertersConsumer = (userInfoRequestConverters) -> { - }; - - private final List authenticationProviders = new ArrayList<>(); - - private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> { - }; - - private AuthenticationSuccessHandler userInfoResponseHandler; - - private AuthenticationFailureHandler errorResponseHandler; - - private Function userInfoMapper; - - /** - * Restrict for internal use only. - * @param objectPostProcessor an {@code ObjectPostProcessor} - */ - OidcUserInfoEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { - super(objectPostProcessor); - } - - /** - * Adds an {@link AuthenticationConverter} used when attempting to extract an UserInfo - * Request from {@link HttpServletRequest} to an instance of - * {@link OidcUserInfoAuthenticationToken} used for authenticating the request. - * @param userInfoRequestConverter an {@link AuthenticationConverter} used when - * attempting to extract an UserInfo Request from {@link HttpServletRequest} - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer userInfoRequestConverter(AuthenticationConverter userInfoRequestConverter) { - Assert.notNull(userInfoRequestConverter, "userInfoRequestConverter cannot be null"); - this.userInfoRequestConverters.add(userInfoRequestConverter); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #userInfoRequestConverter(AuthenticationConverter) - * AuthenticationConverter}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationConverter}. - * @param userInfoRequestConvertersConsumer the {@code Consumer} providing access to - * the {@code List} of default and (optionally) added - * {@link AuthenticationConverter}'s - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer userInfoRequestConverters( - Consumer> userInfoRequestConvertersConsumer) { - Assert.notNull(userInfoRequestConvertersConsumer, "userInfoRequestConvertersConsumer cannot be null"); - this.userInfoRequestConvertersConsumer = userInfoRequestConvertersConsumer; - return this; - } - - /** - * Adds an {@link AuthenticationProvider} used for authenticating an - * {@link OidcUserInfoAuthenticationToken}. - * @param authenticationProvider an {@link AuthenticationProvider} used for - * authenticating an {@link OidcUserInfoAuthenticationToken} - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { - Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); - this.authenticationProviders.add(authenticationProvider); - return this; - } - - /** - * Sets the {@code Consumer} providing access to the {@code List} of default and - * (optionally) added {@link #authenticationProvider(AuthenticationProvider) - * AuthenticationProvider}'s allowing the ability to add, remove, or customize a - * specific {@link AuthenticationProvider}. - * @param authenticationProvidersConsumer the {@code Consumer} providing access to the - * {@code List} of default and (optionally) added {@link AuthenticationProvider}'s - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer authenticationProviders( - Consumer> authenticationProvidersConsumer) { - Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null"); - this.authenticationProvidersConsumer = authenticationProvidersConsumer; - return this; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcUserInfoAuthenticationToken} and returning the {@link OidcUserInfo - * UserInfo Response}. - * @param userInfoResponseHandler the {@link AuthenticationSuccessHandler} used for - * handling an {@link OidcUserInfoAuthenticationToken} - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer userInfoResponseHandler( - AuthenticationSuccessHandler userInfoResponseHandler) { - this.userInfoResponseHandler = userInfoResponseHandler; - return this; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for - * handling an {@link OAuth2AuthenticationException} - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - * @since 0.4.0 - */ - public OidcUserInfoEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) { - this.errorResponseHandler = errorResponseHandler; - return this; - } - - /** - * Sets the {@link Function} used to extract claims from - * {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo} - * for the UserInfo response. - * - *

- * The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the - * {@link OidcUserInfoAuthenticationToken}, as well as, the following context - * attributes: - *

    - *
  • {@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the - * bearer token used to make the request.
  • - *
  • {@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the - * {@link OidcIdToken} and {@link OAuth2AccessToken} associated with the bearer token - * used to make the request.
  • - *
- * @param userInfoMapper the {@link Function} used to extract claims from - * {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo} - * @return the {@link OidcUserInfoEndpointConfigurer} for further configuration - */ - public OidcUserInfoEndpointConfigurer userInfoMapper( - Function userInfoMapper) { - this.userInfoMapper = userInfoMapper; - return this; - } - - @Override - void init(HttpSecurity httpSecurity) { - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) - : authorizationServerSettings.getOidcUserInfoEndpoint(); - this.requestMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, userInfoEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, userInfoEndpointUri)); - - List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); - if (!this.authenticationProviders.isEmpty()) { - authenticationProviders.addAll(0, this.authenticationProviders); - } - this.authenticationProvidersConsumer.accept(authenticationProviders); - authenticationProviders.forEach( - (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider))); - } - - @Override - void configure(HttpSecurity httpSecurity) { - AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); - AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils - .getAuthorizationServerSettings(httpSecurity); - - String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() - ? OAuth2ConfigurerUtils - .withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) - : authorizationServerSettings.getOidcUserInfoEndpoint(); - OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter = new OidcUserInfoEndpointFilter(authenticationManager, - userInfoEndpointUri); - List authenticationConverters = createDefaultAuthenticationConverters(); - if (!this.userInfoRequestConverters.isEmpty()) { - authenticationConverters.addAll(0, this.userInfoRequestConverters); - } - this.userInfoRequestConvertersConsumer.accept(authenticationConverters); - oidcUserInfoEndpointFilter - .setAuthenticationConverter(new DelegatingAuthenticationConverter(authenticationConverters)); - if (this.userInfoResponseHandler != null) { - oidcUserInfoEndpointFilter.setAuthenticationSuccessHandler(this.userInfoResponseHandler); - } - if (this.errorResponseHandler != null) { - oidcUserInfoEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); - } - httpSecurity.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), AuthorizationFilter.class); - } - - @Override - RequestMatcher getRequestMatcher() { - return this.requestMatcher; - } - - private static List createDefaultAuthenticationConverters() { - List authenticationConverters = new ArrayList<>(); - - authenticationConverters.add((request) -> { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return new OidcUserInfoAuthenticationToken(authentication); - }); - - return authenticationConverters; - } - - private List createDefaultAuthenticationProviders(HttpSecurity httpSecurity) { - List authenticationProviders = new ArrayList<>(); - - OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider = new OidcUserInfoAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity)); - if (this.userInfoMapper != null) { - oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper); - } - authenticationProviders.add(oidcUserInfoAuthenticationProvider); - - return authenticationProviders; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java deleted file mode 100644 index 64c3f1bce..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.context; - -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -/** - * A context that holds information of the Authorization Server runtime environment. - * - * @author Joe Grandja - * @since 0.2.2 - * @see AuthorizationServerSettings - * @see AuthorizationServerContextHolder - */ -public interface AuthorizationServerContext { - - /** - * Returns {@link AuthorizationServerSettings#getIssuer()} if available, otherwise, - * resolves the issuer identifier from the "current" request. - * - *

- * The issuer identifier may contain a path component to support - * {@link AuthorizationServerSettings#isMultipleIssuersAllowed() multiple issuers per - * host} in a multi-tenant hosting configuration. - * - *

- * For example: - *

    - *
  • {@code https://example.com/issuer1/oauth2/token} — resolves the issuer to - * {@code https://example.com/issuer1}
  • - *
  • {@code https://example.com/issuer2/oauth2/token} — resolves the issuer to - * {@code https://example.com/issuer2}
  • - *
  • {@code https://example.com/authz/issuer1/oauth2/token} — resolves the - * issuer to {@code https://example.com/authz/issuer1}
  • - *
  • {@code https://example.com/authz/issuer2/oauth2/token} — resolves the - * issuer to {@code https://example.com/authz/issuer2}
  • - *
- * @return {@link AuthorizationServerSettings#getIssuer()} if available, otherwise, - * resolves the issuer identifier from the "current" request - */ - String getIssuer(); - - /** - * Returns the {@link AuthorizationServerSettings}. - * @return the {@link AuthorizationServerSettings} - */ - AuthorizationServerSettings getAuthorizationServerSettings(); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContextHolder.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContextHolder.java deleted file mode 100644 index 0519109c8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContextHolder.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.context; - -/** - * A holder of the {@link AuthorizationServerContext} that associates it with the current - * thread using a {@code ThreadLocal}. - * - * @author Joe Grandja - * @since 0.2.2 - * @see AuthorizationServerContext - */ -public final class AuthorizationServerContextHolder { - - private static final ThreadLocal holder = new ThreadLocal<>(); - - private AuthorizationServerContextHolder() { - } - - /** - * Returns the {@link AuthorizationServerContext} bound to the current thread. - * @return the {@link AuthorizationServerContext} - */ - public static AuthorizationServerContext getContext() { - return holder.get(); - } - - /** - * Bind the given {@link AuthorizationServerContext} to the current thread. - * @param authorizationServerContext the {@link AuthorizationServerContext} - */ - public static void setContext(AuthorizationServerContext authorizationServerContext) { - if (authorizationServerContext == null) { - resetContext(); - } - else { - holder.set(authorizationServerContext); - } - } - - /** - * Reset the {@link AuthorizationServerContext} bound to the current thread. - */ - public static void resetContext() { - holder.remove(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java deleted file mode 100644 index bc0b56704..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.context; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * A facility for holding information associated to a specific context. - * - * @author Joe Grandja - * @since 0.1.0 - */ -public interface Context { - - /** - * Returns the value of the attribute associated to the key. - * @param key the key for the attribute - * @param the type of the value for the attribute - * @return the value of the attribute associated to the key, or {@code null} if not - * available - */ - @Nullable - V get(Object key); - - /** - * Returns the value of the attribute associated to the key. - * @param key the key for the attribute - * @param the type of the value for the attribute - * @return the value of the attribute associated to the key, or {@code null} if not - * available or not of the specified type - */ - @Nullable - default V get(Class key) { - Assert.notNull(key, "key cannot be null"); - V value = get((Object) key); - return key.isInstance(value) ? value : null; - } - - /** - * Returns {@code true} if an attribute associated to the key exists, {@code false} - * otherwise. - * @param key the key for the attribute - * @return {@code true} if an attribute associated to the key exists, {@code false} - * otherwise - */ - boolean hasKey(Object key); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java deleted file mode 100644 index eae996709..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.http.converter; - -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.GsonHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.util.ClassUtils; - -/** - * Utility methods for {@link HttpMessageConverter}'s. - * - * @author Joe Grandja - * @author l uamas - * @since 0.1.1 - */ -final class HttpMessageConverters { - - private static final boolean jackson2Present; - - private static final boolean gsonPresent; - - private static final boolean jsonbPresent; - - static { - ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); - jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) - && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); - gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); - jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); - } - - private HttpMessageConverters() { - } - - static GenericHttpMessageConverter getJsonMessageConverter() { - if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); - } - if (gsonPresent) { - return new GsonHttpMessageConverter(); - } - if (jsonbPresent) { - return new JsonbHttpMessageConverter(); - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java deleted file mode 100644 index f904987a5..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.http.converter; - -import java.net.URL; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; -import org.springframework.util.Assert; - -/** - * A {@link HttpMessageConverter} for an {@link OAuth2AuthorizationServerMetadata OAuth - * 2.0 Authorization Server Metadata Response}. - * - * @author Daniel Garnier-Moiroux - * @since 0.1.1 - * @see AbstractHttpMessageConverter - * @see OAuth2AuthorizationServerMetadata - */ -public class OAuth2AuthorizationServerMetadataHttpMessageConverter - extends AbstractHttpMessageConverter { - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private Converter, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter(); - - private Converter> authorizationServerMetadataParametersConverter = OAuth2AuthorizationServerMetadata::getClaims; - - public OAuth2AuthorizationServerMetadataHttpMessageConverter() { - super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); - } - - @Override - protected boolean supports(Class clazz) { - return OAuth2AuthorizationServerMetadata.class.isAssignableFrom(clazz); - } - - @Override - @SuppressWarnings("unchecked") - protected OAuth2AuthorizationServerMetadata readInternal(Class clazz, - HttpInputMessage inputMessage) throws HttpMessageNotReadableException { - try { - Map authorizationServerMetadataParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); - return this.authorizationServerMetadataConverter.convert(authorizationServerMetadataParameters); - } - catch (Exception ex) { - throw new HttpMessageNotReadableException( - "An error occurred reading the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex, - inputMessage); - } - } - - @Override - protected void writeInternal(OAuth2AuthorizationServerMetadata authorizationServerMetadata, - HttpOutputMessage outputMessage) throws HttpMessageNotWritableException { - try { - Map authorizationServerMetadataResponseParameters = this.authorizationServerMetadataParametersConverter - .convert(authorizationServerMetadata); - this.jsonMessageConverter.write(authorizationServerMetadataResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); - } - catch (Exception ex) { - throw new HttpMessageNotWritableException( - "An error occurred writing the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex); - } - } - - /** - * Sets the {@link Converter} used for converting the OAuth 2.0 Authorization Server - * Metadata parameters to an {@link OAuth2AuthorizationServerMetadata}. - * @param authorizationServerMetadataConverter the {@link Converter} used for - * converting to an {@link OAuth2AuthorizationServerMetadata}. - */ - public final void setAuthorizationServerMetadataConverter( - Converter, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter) { - Assert.notNull(authorizationServerMetadataConverter, "authorizationServerMetadataConverter cannot be null"); - this.authorizationServerMetadataConverter = authorizationServerMetadataConverter; - } - - /** - * Sets the {@link Converter} used for converting the - * {@link OAuth2AuthorizationServerMetadata} to a {@code Map} representation of the - * OAuth 2.0 Authorization Server Metadata. - * @param authorizationServerMetadataParametersConverter the {@link Converter} used - * for converting to a {@code Map} representation of the OAuth 2.0 Authorization - * Server Metadata. - */ - public final void setAuthorizationServerMetadataParametersConverter( - Converter> authorizationServerMetadataParametersConverter) { - Assert.notNull(authorizationServerMetadataParametersConverter, - "authorizationServerMetadataParametersConverter cannot be null"); - this.authorizationServerMetadataParametersConverter = authorizationServerMetadataParametersConverter; - } - - private static final class OAuth2AuthorizationServerMetadataConverter - implements Converter, OAuth2AuthorizationServerMetadata> { - - private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService - .getSharedInstance(); - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); - - private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class); - - private final ClaimTypeConverter claimTypeConverter; - - private OAuth2AuthorizationServerMetadataConverter() { - Converter collectionStringConverter = getConverter( - TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR)); - Converter urlConverter = getConverter(URL_TYPE_DESCRIPTOR); - - Map> claimConverters = new HashMap<>(); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, - urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT, - urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, urlConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, urlConverter); - claimConverters.put( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, - collectionStringConverter); - claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED, - collectionStringConverter); - this.claimTypeConverter = new ClaimTypeConverter(claimConverters); - } - - @Override - public OAuth2AuthorizationServerMetadata convert(Map source) { - Map parsedClaims = this.claimTypeConverter.convert(source); - return OAuth2AuthorizationServerMetadata.withClaims(parsedClaims).build(); - } - - private static Converter getConverter(TypeDescriptor targetDescriptor) { - return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java deleted file mode 100644 index f64cd7de9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.http.converter; - -import java.net.URL; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * A {@link HttpMessageConverter} for an {@link OAuth2TokenIntrospection OAuth 2.0 Token - * Introspection Response}. - * - * @author Gerardo Roza - * @author Joe Grandja - * @since 0.1.1 - * @see AbstractHttpMessageConverter - * @see OAuth2TokenIntrospection - */ -public class OAuth2TokenIntrospectionHttpMessageConverter - extends AbstractHttpMessageConverter { - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private Converter, OAuth2TokenIntrospection> tokenIntrospectionConverter = new MapOAuth2TokenIntrospectionConverter(); - - private Converter> tokenIntrospectionParametersConverter = new OAuth2TokenIntrospectionMapConverter(); - - public OAuth2TokenIntrospectionHttpMessageConverter() { - super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); - } - - @Override - protected boolean supports(Class clazz) { - return OAuth2TokenIntrospection.class.isAssignableFrom(clazz); - } - - @Override - @SuppressWarnings("unchecked") - protected OAuth2TokenIntrospection readInternal(Class clazz, - HttpInputMessage inputMessage) throws HttpMessageNotReadableException { - try { - Map tokenIntrospectionParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); - return this.tokenIntrospectionConverter.convert(tokenIntrospectionParameters); - } - catch (Exception ex) { - throw new HttpMessageNotReadableException( - "An error occurred reading the Token Introspection Response: " + ex.getMessage(), ex, inputMessage); - } - } - - @Override - protected void writeInternal(OAuth2TokenIntrospection tokenIntrospection, HttpOutputMessage outputMessage) - throws HttpMessageNotWritableException { - try { - Map tokenIntrospectionResponseParameters = this.tokenIntrospectionParametersConverter - .convert(tokenIntrospection); - this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); - } - catch (Exception ex) { - throw new HttpMessageNotWritableException( - "An error occurred writing the Token Introspection Response: " + ex.getMessage(), ex); - } - } - - /** - * Sets the {@link Converter} used for converting the Token Introspection Response - * parameters to an {@link OAuth2TokenIntrospection}. - * @param tokenIntrospectionConverter the {@link Converter} used for converting to an - * {@link OAuth2TokenIntrospection} - */ - public final void setTokenIntrospectionConverter( - Converter, OAuth2TokenIntrospection> tokenIntrospectionConverter) { - Assert.notNull(tokenIntrospectionConverter, "tokenIntrospectionConverter cannot be null"); - this.tokenIntrospectionConverter = tokenIntrospectionConverter; - } - - /** - * Sets the {@link Converter} used for converting an {@link OAuth2TokenIntrospection} - * to a {@code Map} representation of the Token Introspection Response parameters. - * @param tokenIntrospectionParametersConverter the {@link Converter} used for - * converting to a {@code Map} representation of the Token Introspection Response - * parameters - */ - public final void setTokenIntrospectionParametersConverter( - Converter> tokenIntrospectionParametersConverter) { - Assert.notNull(tokenIntrospectionParametersConverter, "tokenIntrospectionParametersConverter cannot be null"); - this.tokenIntrospectionParametersConverter = tokenIntrospectionParametersConverter; - } - - private static final class MapOAuth2TokenIntrospectionConverter - implements Converter, OAuth2TokenIntrospection> { - - private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService - .getSharedInstance(); - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class); - - private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); - - private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class); - - private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class); - - private final ClaimTypeConverter claimTypeConverter; - - private MapOAuth2TokenIntrospectionConverter() { - Converter booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR); - Converter stringConverter = getConverter(STRING_TYPE_DESCRIPTOR); - Converter instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR); - Converter collectionStringConverter = getConverter( - TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR)); - Converter urlConverter = getConverter(URL_TYPE_DESCRIPTOR); - - Map> claimConverters = new HashMap<>(); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.ACTIVE, booleanConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.SCOPE, - MapOAuth2TokenIntrospectionConverter::convertScope); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, stringConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.USERNAME, stringConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, stringConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.EXP, instantConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.IAT, instantConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.NBF, instantConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.SUB, stringConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.AUD, collectionStringConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.ISS, urlConverter); - claimConverters.put(OAuth2TokenIntrospectionClaimNames.JTI, stringConverter); - this.claimTypeConverter = new ClaimTypeConverter(claimConverters); - } - - @Override - public OAuth2TokenIntrospection convert(Map source) { - Map parsedClaims = this.claimTypeConverter.convert(source); - return OAuth2TokenIntrospection.withClaims(parsedClaims).build(); - } - - private static Converter getConverter(TypeDescriptor targetDescriptor) { - return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); - } - - private static List convertScope(Object scope) { - if (scope == null) { - return Collections.emptyList(); - } - return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " ")); - } - - } - - private static final class OAuth2TokenIntrospectionMapConverter - implements Converter> { - - @Override - public Map convert(OAuth2TokenIntrospection source) { - Map responseClaims = new LinkedHashMap<>(source.getClaims()); - if (!CollectionUtils.isEmpty(source.getScopes())) { - responseClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, - StringUtils.collectionToDelimitedString(source.getScopes(), " ")); - } - if (source.getExpiresAt() != null) { - responseClaims.put(OAuth2TokenIntrospectionClaimNames.EXP, source.getExpiresAt().getEpochSecond()); - } - if (source.getIssuedAt() != null) { - responseClaims.put(OAuth2TokenIntrospectionClaimNames.IAT, source.getIssuedAt().getEpochSecond()); - } - if (source.getNotBefore() != null) { - responseClaims.put(OAuth2TokenIntrospectionClaimNames.NBF, source.getNotBefore().getEpochSecond()); - } - return responseClaims; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/DurationMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/DurationMixin.java deleted file mode 100644 index cfd950035..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/DurationMixin.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.jackson2; - -import java.time.Duration; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -/** - * This mixin class is used to serialize/deserialize {@link Duration}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see Duration - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE, - creatorVisibility = JsonAutoDetect.Visibility.NONE) -abstract class DurationMixin { - - @JsonCreator - static void ofSeconds(@JsonProperty("seconds") long seconds, @JsonProperty("nano") long nanoAdjustment) { - } - - @JsonGetter("seconds") - abstract long getSeconds(); - - @JsonGetter("nano") - abstract int getNano(); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java deleted file mode 100644 index 6f49c9537..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.jackson2; - -import java.util.HashSet; -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -/** - * This mixin class is used to serialize/deserialize {@link HashSet}. - * - * @author Steve Riesenberg - * @since 0.1.2 - * @see HashSet - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -abstract class HashSetMixin { - - @JsonCreator - HashSetMixin(Set set) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java deleted file mode 100644 index 2dd12dbcf..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Utility class for {@code JsonNode}. - * - * @author Joe Grandja - * @since 0.1.2 - */ -abstract class JsonNodeUtils { - - static final TypeReference> STRING_SET = new TypeReference<>() { - }; - - static final TypeReference> STRING_OBJECT_MAP = new TypeReference<>() { - }; - - static String findStringValue(JsonNode jsonNode, String fieldName) { - if (jsonNode == null) { - return null; - } - JsonNode value = jsonNode.findValue(fieldName); - return (value != null && value.isTextual()) ? value.asText() : null; - } - - static T findValue(JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, - ObjectMapper mapper) { - if (jsonNode == null) { - return null; - } - JsonNode value = jsonNode.findValue(fieldName); - return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null; - } - - static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) { - if (jsonNode == null) { - return null; - } - JsonNode value = jsonNode.findValue(fieldName); - return (value != null && value.isObject()) ? value : null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JwsAlgorithmMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JwsAlgorithmMixin.java deleted file mode 100644 index fc7dd6c61..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JwsAlgorithmMixin.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.jackson2; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; - -/** - * This mixin class is used to serialize/deserialize {@link SignatureAlgorithm}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see SignatureAlgorithm - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE) -abstract class JwsAlgorithmMixin { - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java deleted file mode 100644 index a8605c075..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.jackson2; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder; - -/** - * A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationRequest - * @see OAuth2AuthorizationRequestMixin - */ -final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer { - - @Override - public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) - throws IOException { - ObjectMapper mapper = (ObjectMapper) parser.getCodec(); - JsonNode root = mapper.readTree(parser); - return deserialize(parser, mapper, root); - } - - private OAuth2AuthorizationRequest deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root) - throws JsonParseException { - AuthorizationGrantType authorizationGrantType = convertAuthorizationGrantType( - JsonNodeUtils.findObjectNode(root, "authorizationGrantType")); - Builder builder = getBuilder(parser, authorizationGrantType); - builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri")); - builder.clientId(JsonNodeUtils.findStringValue(root, "clientId")); - builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri")); - builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, mapper)); - builder.state(JsonNodeUtils.findStringValue(root, "state")); - builder.additionalParameters( - JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, mapper)); - builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri")); - builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, mapper)); - return builder.build(); - } - - private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) - throws JsonParseException { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { - return OAuth2AuthorizationRequest.authorizationCode(); - } - throw new JsonParseException(parser, "Invalid authorizationGrantType"); - } - - private static AuthorizationGrantType convertAuthorizationGrantType(JsonNode jsonNode) { - String value = JsonNodeUtils.findStringValue(jsonNode, "value"); - if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) { - return AuthorizationGrantType.AUTHORIZATION_CODE; - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java deleted file mode 100644 index 0bf7c9a8f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.jackson2; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; - -/** - * This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}. - * It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see OAuth2AuthorizationRequest - * @see OAuth2AuthorizationRequestDeserializer - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE) -@JsonIgnoreProperties(ignoreUnknown = true) -abstract class OAuth2AuthorizationRequestMixin { - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java deleted file mode 100644 index 7219a95b0..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import java.time.Duration; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; - -import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.module.SimpleModule; - -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; - -/** - * Jackson {@code Module} for {@code spring-authorization-server}, that registers the - * following mix-in annotations: - * - *
    - *
  • {@link UnmodifiableMapMixin}
  • - *
  • {@link HashSetMixin}
  • - *
  • {@link OAuth2AuthorizationRequestMixin}
  • - *
  • {@link OAuth2TokenExchangeCompositeAuthenticationTokenMixin}
  • - *
  • {@link DurationMixin}
  • - *
  • {@link JwsAlgorithmMixin}
  • - *
  • {@link OAuth2TokenFormatMixin}
  • - *
  • {@link StringArrayMixin}
  • - *
- * - * If not already enabled, default typing will be automatically enabled as type info is - * required to properly serialize/deserialize objects. In order to use this module just - * add it to your {@code ObjectMapper} configuration. - * - *
- *     ObjectMapper mapper = new ObjectMapper();
- *     mapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
- * 
- * - * NOTE: Use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get a list - * of all security modules. - * - * @author Steve Riesenberg - * @since 0.1.2 - * @see SecurityJackson2Modules - * @see UnmodifiableMapMixin - * @see HashSetMixin - * @see OAuth2AuthorizationRequestMixin - * @see DurationMixin - * @see JwsAlgorithmMixin - * @see OAuth2TokenFormatMixin - * @see StringArrayMixin - */ -public class OAuth2AuthorizationServerJackson2Module extends SimpleModule { - - public OAuth2AuthorizationServerJackson2Module() { - super(OAuth2AuthorizationServerJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null)); - } - - @Override - public void setupModule(SetupContext context) { - SecurityJackson2Modules.enableDefaultTyping(context.getOwner()); - context.setMixInAnnotations(Collections.unmodifiableMap(Collections.emptyMap()).getClass(), - UnmodifiableMapMixin.class); - context.setMixInAnnotations(HashSet.class, HashSetMixin.class); - context.setMixInAnnotations(LinkedHashSet.class, HashSetMixin.class); - context.setMixInAnnotations(OAuth2TokenExchangeActor.class, OAuth2TokenExchangeActorMixin.class); - context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class); - context.setMixInAnnotations(OAuth2TokenExchangeCompositeAuthenticationToken.class, - OAuth2TokenExchangeCompositeAuthenticationTokenMixin.class); - context.setMixInAnnotations(Duration.class, DurationMixin.class); - context.setMixInAnnotations(SignatureAlgorithm.class, JwsAlgorithmMixin.class); - context.setMixInAnnotations(MacAlgorithm.class, JwsAlgorithmMixin.class); - context.setMixInAnnotations(OAuth2TokenFormat.class, OAuth2TokenFormatMixin.class); - context.setMixInAnnotations(String[].class, StringArrayMixin.class); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java deleted file mode 100644 index 5d928b8b1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; - -/** - * This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeActor}. - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2TokenExchangeActor - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE) -@JsonIgnoreProperties(ignoreUnknown = true) -abstract class OAuth2TokenExchangeActorMixin { - - @JsonCreator - OAuth2TokenExchangeActorMixin(@JsonProperty("claims") Map claims) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java deleted file mode 100644 index be55f3120..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; - -/** - * This mixin class is used to serialize/deserialize - * {@link OAuth2TokenExchangeCompositeAuthenticationToken}. - * - * @author Steve Riesenberg - * @since 1.3 - * @see OAuth2TokenExchangeCompositeAuthenticationToken - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE) -@JsonIgnoreProperties(ignoreUnknown = true) -abstract class OAuth2TokenExchangeCompositeAuthenticationTokenMixin { - - @JsonCreator - OAuth2TokenExchangeCompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject, - @JsonProperty("actors") List actors) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenFormatMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenFormatMixin.java deleted file mode 100644 index 1fde186c1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenFormatMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.jackson2; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; - -/** - * This mixin class is used to serialize/deserialize {@link OAuth2TokenFormat}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenFormat - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE) -abstract class OAuth2TokenFormatMixin { - - @JsonCreator - OAuth2TokenFormatMixin(@JsonProperty("value") String value) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StringArrayMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StringArrayMixin.java deleted file mode 100644 index 48bc7980b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StringArrayMixin.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -/** - * This mixin class is used to serialize/deserialize {@link String} array. - * - * @author Nikola Jovanovic - * @since 1.2.6 - * @see String - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -abstract class StringArrayMixin { - - @JsonCreator - StringArrayMixin(String[] array) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java deleted file mode 100644 index a8e362e26..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.jackson2; - -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * A {@code JsonDeserializer} for {@link Collections#unmodifiableMap(Map)}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see Collections#unmodifiableMap(Map) - * @see UnmodifiableMapMixin - */ -final class UnmodifiableMapDeserializer extends JsonDeserializer> { - - @Override - public Map deserialize(JsonParser parser, DeserializationContext context) throws IOException { - ObjectMapper mapper = (ObjectMapper) parser.getCodec(); - JsonNode mapNode = mapper.readTree(parser); - Map result = new LinkedHashMap<>(); - if (mapNode != null && mapNode.isObject()) { - Iterable> fields = mapNode::fields; - for (Map.Entry field : fields) { - result.put(field.getKey(), mapper.readValue(field.getValue().traverse(mapper), Object.class)); - } - } - return Collections.unmodifiableMap(result); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java deleted file mode 100644 index 047735fed..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.jackson2; - -import java.util.Collections; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * This mixin class is used to serialize/deserialize - * {@link Collections#unmodifiableMap(Map)}. It also registers a custom deserializer - * {@link UnmodifiableMapDeserializer}. - * - * @author Joe Grandja - * @since 0.1.2 - * @see Collections#unmodifiableMap(Map) - * @see UnmodifiableMapDeserializer - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonDeserialize(using = UnmodifiableMapDeserializer.class) -abstract class UnmodifiableMapMixin { - - @JsonCreator - UnmodifiableMapMixin(Map map) { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java deleted file mode 100644 index c0eb33b67..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import java.net.URL; -import java.time.Instant; -import java.util.List; - -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; - -/** - * A {@link ClaimAccessor} for the "claims" that are contained in the OpenID Client - * Registration Request and Response. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.1.1 - * @see ClaimAccessor - * @see OidcClientMetadataClaimNames - * @see OidcClientRegistration - * @see 2. - * Client Metadata - * @see 3.1. - * Client Registration Metadata - */ -public interface OidcClientMetadataClaimAccessor extends ClaimAccessor { - - /** - * Returns the Client Identifier {@code (client_id)}. - * @return the Client Identifier - */ - default String getClientId() { - return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_ID); - } - - /** - * Returns the time at which the Client Identifier was issued - * {@code (client_id_issued_at)}. - * @return the time at which the Client Identifier was issued - */ - default Instant getClientIdIssuedAt() { - return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT); - } - - /** - * Returns the Client Secret {@code (client_secret)}. - * @return the Client Secret - */ - default String getClientSecret() { - return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_SECRET); - } - - /** - * Returns the time at which the {@code client_secret} will expire - * {@code (client_secret_expires_at)}. - * @return the time at which the {@code client_secret} will expire - */ - default Instant getClientSecretExpiresAt() { - return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT); - } - - /** - * Returns the name of the Client to be presented to the End-User - * {@code (client_name)}. - * @return the name of the Client to be presented to the End-User - */ - default String getClientName() { - return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_NAME); - } - - /** - * Returns the redirection {@code URI} values used by the Client - * {@code (redirect_uris)}. - * @return the redirection {@code URI} values used by the Client - */ - default List getRedirectUris() { - return getClaimAsStringList(OidcClientMetadataClaimNames.REDIRECT_URIS); - } - - /** - * Returns the post logout redirection {@code URI} values used by the Client - * {@code (post_logout_redirect_uris)}. The {@code post_logout_redirect_uri} parameter - * is used by the client when requesting that the End-User's User Agent be redirected - * to after a logout has been performed. - * @return the post logout redirection {@code URI} values used by the Client - * @since 1.1 - */ - default List getPostLogoutRedirectUris() { - return getClaimAsStringList(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); - } - - /** - * Returns the authentication method used by the Client for the Token Endpoint - * {@code (token_endpoint_auth_method)}. - * @return the authentication method used by the Client for the Token Endpoint - */ - default String getTokenEndpointAuthenticationMethod() { - return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - } - - /** - * Returns the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint for the - * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and - * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} - * authentication methods {@code (token_endpoint_auth_signing_alg)}. - * @return the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint - * @since 0.2.2 - */ - default String getTokenEndpointAuthenticationSigningAlgorithm() { - return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG); - } - - /** - * Returns the OAuth 2.0 {@code grant_type} values that the Client will restrict - * itself to using {@code (grant_types)}. - * @return the OAuth 2.0 {@code grant_type} values that the Client will restrict - * itself to using - */ - default List getGrantTypes() { - return getClaimAsStringList(OidcClientMetadataClaimNames.GRANT_TYPES); - } - - /** - * Returns the OAuth 2.0 {@code response_type} values that the Client will restrict - * itself to using {@code (response_types)}. - * @return the OAuth 2.0 {@code response_type} values that the Client will restrict - * itself to using - */ - default List getResponseTypes() { - return getClaimAsStringList(OidcClientMetadataClaimNames.RESPONSE_TYPES); - } - - /** - * Returns the OAuth 2.0 {@code scope} values that the Client will restrict itself to - * using {@code (scope)}. - * @return the OAuth 2.0 {@code scope} values that the Client will restrict itself to - * using - */ - default List getScopes() { - return getClaimAsStringList(OidcClientMetadataClaimNames.SCOPE); - } - - /** - * Returns the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)}. - * @return the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)} - * @since 0.2.2 - */ - default URL getJwkSetUrl() { - return getClaimAsURL(OidcClientMetadataClaimNames.JWKS_URI); - } - - /** - * Returns the {@link SignatureAlgorithm JWS} algorithm required for signing the - * {@link OidcIdToken ID Token} issued to the Client - * {@code (id_token_signed_response_alg)}. - * @return the {@link SignatureAlgorithm JWS} algorithm required for signing the - * {@link OidcIdToken ID Token} issued to the Client - */ - default String getIdTokenSignedResponseAlgorithm() { - return getClaimAsString(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG); - } - - /** - * Returns the Registration Access Token that can be used at the Client Configuration - * Endpoint. - * @return the Registration Access Token that can be used at the Client Configuration - * Endpoint - * @since 0.2.1 - */ - default String getRegistrationAccessToken() { - return getClaimAsString(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN); - } - - /** - * Returns the {@code URL} of the Client Configuration Endpoint where the Registration - * Access Token can be used. - * @return the {@code URL} of the Client Configuration Endpoint where the Registration - * Access Token can be used - * @since 0.2.1 - */ - default URL getRegistrationClientUrl() { - return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java deleted file mode 100644 index 6d8359585..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimNames.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; - -/** - * The names of the "claims" defined by OpenID Connect Dynamic Client Registration 1.0 - * that are contained in the OpenID Client Registration Request and Response. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.1.1 - * @see 2. - * Client Metadata - * @see 3.1. - * Client Registration Metadata - */ -public final class OidcClientMetadataClaimNames { - - /** - * {@code client_id} - the Client Identifier - */ - public static final String CLIENT_ID = "client_id"; - - /** - * {@code client_id_issued_at} - the time at which the Client Identifier was issued - */ - public static final String CLIENT_ID_ISSUED_AT = "client_id_issued_at"; - - /** - * {@code client_secret} - the Client Secret - */ - public static final String CLIENT_SECRET = "client_secret"; - - /** - * {@code client_secret_expires_at} - the time at which the {@code client_secret} will - * expire or 0 if it will not expire - */ - public static final String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at"; - - /** - * {@code client_name} - the name of the Client to be presented to the End-User - */ - public static final String CLIENT_NAME = "client_name"; - - /** - * {@code redirect_uris} - the redirection {@code URI} values used by the Client - */ - public static final String REDIRECT_URIS = "redirect_uris"; - - /** - * {@code post_logout_redirect_uris} - the post logout redirection {@code URI} values - * used by the Client. The {@code post_logout_redirect_uri} parameter is used by the - * client when requesting that the End-User's User Agent be redirected to after a - * logout has been performed. - * @since 1.1 - */ - public static final String POST_LOGOUT_REDIRECT_URIS = "post_logout_redirect_uris"; - - /** - * {@code token_endpoint_auth_method} - the authentication method used by the Client - * for the Token Endpoint - */ - public static final String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method"; - - /** - * {@code token_endpoint_auth_signing_alg} - the {@link JwsAlgorithm JWS} algorithm - * that must be used for signing the {@link Jwt JWT} used to authenticate the Client - * at the Token Endpoint for the {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT - * private_key_jwt} and {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT - * client_secret_jwt} authentication methods - * @since 0.2.2 - */ - public static final String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "token_endpoint_auth_signing_alg"; - - /** - * {@code grant_types} - the OAuth 2.0 {@code grant_type} values that the Client will - * restrict itself to using - */ - public static final String GRANT_TYPES = "grant_types"; - - /** - * {@code response_types} - the OAuth 2.0 {@code response_type} values that the Client - * will restrict itself to using - */ - public static final String RESPONSE_TYPES = "response_types"; - - /** - * {@code scope} - a space-separated list of OAuth 2.0 {@code scope} values that the - * Client will restrict itself to using - */ - public static final String SCOPE = "scope"; - - /** - * {@code jwks_uri} - the {@code URL} for the Client's JSON Web Key Set - * @since 0.2.2 - */ - public static final String JWKS_URI = "jwks_uri"; - - /** - * {@code id_token_signed_response_alg} - the {@link JwsAlgorithm JWS} algorithm - * required for signing the {@link OidcIdToken ID Token} issued to the Client - */ - public static final String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg"; - - /** - * {@code registration_access_token} - the Registration Access Token that can be used - * at the Client Configuration Endpoint - * @since 0.2.1 - */ - public static final String REGISTRATION_ACCESS_TOKEN = "registration_access_token"; - - /** - * {@code registration_client_uri} - the {@code URL} of the Client Configuration - * Endpoint where the Registration Access Token can be used - * @since 0.2.1 - */ - public static final String REGISTRATION_CLIENT_URI = "registration_client_uri"; - - private OidcClientMetadataClaimNames() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java deleted file mode 100644 index 061dc2d12..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistration.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc; - -import java.io.Serial; -import java.io.Serializable; -import java.net.URI; -import java.net.URL; -import java.time.Instant; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.util.Assert; - -/** - * A representation of an OpenID Client Registration Request and Response, which is sent - * to and returned from the Client Registration Endpoint, and contains a set of claims - * about the Client's Registration information. The claims are defined by the OpenID - * Connect Dynamic Client Registration 1.0 specification. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.1.1 - * @see OidcClientMetadataClaimAccessor - * @see 3.1. - * Client Registration Request - * @see 3.2. - * Client Registration Response - * @see 3.1. - * Client Registration Metadata - */ -public final class OidcClientRegistration implements OidcClientMetadataClaimAccessor, Serializable { - - @Serial - private static final long serialVersionUID = 6518710174552040014L; - - private final Map claims; - - private OidcClientRegistration(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims)); - } - - /** - * Returns the metadata as claims. - * @return a {@code Map} of the metadata as claims - */ - @Override - public Map getClaims() { - return this.claims; - } - - /** - * Constructs a new {@link Builder} with empty claims. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Constructs a new {@link Builder} with the provided claims. - * @param claims the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder withClaims(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - return new Builder().claims((c) -> c.putAll(claims)); - } - - /** - * Helps configure an {@link OidcClientRegistration}. - */ - public static final class Builder { - - private final Map claims = new LinkedHashMap<>(); - - private Builder() { - } - - /** - * Sets the Client Identifier, REQUIRED. - * @param clientId the Client Identifier - * @return the {@link Builder} for further configuration - */ - public Builder clientId(String clientId) { - return claim(OidcClientMetadataClaimNames.CLIENT_ID, clientId); - } - - /** - * Sets the time at which the Client Identifier was issued, OPTIONAL. - * @param clientIdIssuedAt the time at which the Client Identifier was issued - * @return the {@link Builder} for further configuration - */ - public Builder clientIdIssuedAt(Instant clientIdIssuedAt) { - return claim(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, clientIdIssuedAt); - } - - /** - * Sets the Client Secret, OPTIONAL. - * @param clientSecret the Client Secret - * @return the {@link Builder} for further configuration - */ - public Builder clientSecret(String clientSecret) { - return claim(OidcClientMetadataClaimNames.CLIENT_SECRET, clientSecret); - } - - /** - * Sets the time at which the {@code client_secret} will expire or {@code null} if - * it will not expire, REQUIRED if {@code client_secret} was issued. - * @param clientSecretExpiresAt the time at which the {@code client_secret} will - * expire or {@code null} if it will not expire - * @return the {@link Builder} for further configuration - */ - public Builder clientSecretExpiresAt(Instant clientSecretExpiresAt) { - return claim(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt); - } - - /** - * Sets the name of the Client to be presented to the End-User, OPTIONAL. - * @param clientName the name of the Client to be presented to the End-User - * @return the {@link Builder} for further configuration - */ - public Builder clientName(String clientName) { - return claim(OidcClientMetadataClaimNames.CLIENT_NAME, clientName); - } - - /** - * Add the redirection {@code URI} used by the Client, REQUIRED. - * @param redirectUri the redirection {@code URI} used by the Client - * @return the {@link Builder} for further configuration - */ - public Builder redirectUri(String redirectUri) { - addClaimToClaimList(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUri); - return this; - } - - /** - * A {@code Consumer} of the redirection {@code URI} values used by the Client, - * allowing the ability to add, replace, or remove, REQUIRED. - * @param redirectUrisConsumer a {@code Consumer} of the redirection {@code URI} - * values used by the Client - * @return the {@link Builder} for further configuration - */ - public Builder redirectUris(Consumer> redirectUrisConsumer) { - acceptClaimValues(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUrisConsumer); - return this; - } - - /** - * Add the post logout redirection {@code URI} used by the Client, OPTIONAL. The - * {@code post_logout_redirect_uri} parameter is used by the client when - * requesting that the End-User's User Agent be redirected to after a logout has - * been performed. - * @param postLogoutRedirectUri the post logout redirection {@code URI} used by - * the Client - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder postLogoutRedirectUri(String postLogoutRedirectUri) { - addClaimToClaimList(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUri); - return this; - } - - /** - * A {@code Consumer} of the post logout redirection {@code URI} values used by - * the Client, allowing the ability to add, replace, or remove, OPTIONAL. - * @param postLogoutRedirectUrisConsumer a {@code Consumer} of the post logout - * redirection {@code URI} values used by the Client - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder postLogoutRedirectUris(Consumer> postLogoutRedirectUrisConsumer) { - acceptClaimValues(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, postLogoutRedirectUrisConsumer); - return this; - } - - /** - * Sets the authentication method used by the Client for the Token Endpoint, - * OPTIONAL. - * @param tokenEndpointAuthenticationMethod the authentication method used by the - * Client for the Token Endpoint - * @return the {@link Builder} for further configuration - */ - public Builder tokenEndpointAuthenticationMethod(String tokenEndpointAuthenticationMethod) { - return claim(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, tokenEndpointAuthenticationMethod); - } - - /** - * Sets the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint for the - * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and - * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} - * authentication methods, OPTIONAL. - * @param authenticationSigningAlgorithm the {@link JwsAlgorithm JWS} algorithm - * that must be used for signing the {@link Jwt JWT} used to authenticate the - * Client at the Token Endpoint - * @return the {@link Builder} for further configuration - * @since 0.2.2 - */ - public Builder tokenEndpointAuthenticationSigningAlgorithm(String authenticationSigningAlgorithm) { - return claim(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, authenticationSigningAlgorithm); - } - - /** - * Add the OAuth 2.0 {@code grant_type} that the Client will restrict itself to - * using, OPTIONAL. - * @param grantType the OAuth 2.0 {@code grant_type} that the Client will restrict - * itself to using - * @return the {@link Builder} for further configuration - */ - public Builder grantType(String grantType) { - addClaimToClaimList(OidcClientMetadataClaimNames.GRANT_TYPES, grantType); - return this; - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code grant_type} values that the Client - * will restrict itself to using, allowing the ability to add, replace, or remove, - * OPTIONAL. - * @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 - * {@code grant_type} values that the Client will restrict itself to using - * @return the {@link Builder} for further configuration - */ - public Builder grantTypes(Consumer> grantTypesConsumer) { - acceptClaimValues(OidcClientMetadataClaimNames.GRANT_TYPES, grantTypesConsumer); - return this; - } - - /** - * Add the OAuth 2.0 {@code response_type} that the Client will restrict itself to - * using, OPTIONAL. - * @param responseType the OAuth 2.0 {@code response_type} that the Client will - * restrict itself to using - * @return the {@link Builder} for further configuration - */ - public Builder responseType(String responseType) { - addClaimToClaimList(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseType); - return this; - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code response_type} values that the - * Client will restrict itself to using, allowing the ability to add, replace, or - * remove, OPTIONAL. - * @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 - * {@code response_type} values that the Client will restrict itself to using - * @return the {@link Builder} for further configuration - */ - public Builder responseTypes(Consumer> responseTypesConsumer) { - acceptClaimValues(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseTypesConsumer); - return this; - } - - /** - * Add the OAuth 2.0 {@code scope} that the Client will restrict itself to using, - * OPTIONAL. - * @param scope the OAuth 2.0 {@code scope} that the Client will restrict itself - * to using - * @return the {@link Builder} for further configuration - */ - public Builder scope(String scope) { - addClaimToClaimList(OidcClientMetadataClaimNames.SCOPE, scope); - return this; - } - - /** - * A {@code Consumer} of the OAuth 2.0 {@code scope} values that the Client will - * restrict itself to using, allowing the ability to add, replace, or remove, - * OPTIONAL. - * @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values - * that the Client will restrict itself to using - * @return the {@link Builder} for further configuration - */ - public Builder scopes(Consumer> scopesConsumer) { - acceptClaimValues(OidcClientMetadataClaimNames.SCOPE, scopesConsumer); - return this; - } - - /** - * Sets the {@code URL} for the Client's JSON Web Key Set, OPTIONAL. - * @param jwkSetUrl the {@code URL} for the Client's JSON Web Key Set - * @return the {@link Builder} for further configuration - * @since 0.2.2 - */ - public Builder jwkSetUrl(String jwkSetUrl) { - return claim(OidcClientMetadataClaimNames.JWKS_URI, jwkSetUrl); - } - - /** - * Sets the {@link SignatureAlgorithm JWS} algorithm required for signing the - * {@link OidcIdToken ID Token} issued to the Client, OPTIONAL. - * @param idTokenSignedResponseAlgorithm the {@link SignatureAlgorithm JWS} - * algorithm required for signing the {@link OidcIdToken ID Token} issued to the - * Client - * @return the {@link Builder} for further configuration - */ - public Builder idTokenSignedResponseAlgorithm(String idTokenSignedResponseAlgorithm) { - return claim(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, idTokenSignedResponseAlgorithm); - } - - /** - * Sets the Registration Access Token that can be used at the Client Configuration - * Endpoint, OPTIONAL. - * @param registrationAccessToken the Registration Access Token that can be used - * at the Client Configuration Endpoint - * @return the {@link Builder} for further configuration - * @since 0.2.1 - */ - public Builder registrationAccessToken(String registrationAccessToken) { - return claim(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, registrationAccessToken); - } - - /** - * Sets the {@code URL} of the Client Configuration Endpoint where the - * Registration Access Token can be used, OPTIONAL. - * @param registrationClientUrl the {@code URL} of the Client Configuration - * Endpoint where the Registration Access Token can be used - * @return the {@link Builder} for further configuration - * @since 0.2.1 - */ - public Builder registrationClientUrl(String registrationClientUrl) { - return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUrl); - } - - /** - * Sets the claim. - * @param name the claim name - * @param value the claim value - * @return the {@link Builder} for further configuration - */ - public Builder claim(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.put(name, value); - return this; - } - - /** - * Provides access to every {@link #claim(String, Object)} declared so far - * allowing the ability to add, replace, or remove. - * @param claimsConsumer a {@code Consumer} of the claims - * @return the {@link Builder} for further configurations - */ - public Builder claims(Consumer> claimsConsumer) { - claimsConsumer.accept(this.claims); - return this; - } - - /** - * Validate the claims and build the {@link OidcClientRegistration}. - *

- * The following claims are REQUIRED: {@code client_id}, {@code redirect_uris}. - * @return the {@link OidcClientRegistration} - */ - public OidcClientRegistration build() { - validate(); - return new OidcClientRegistration(this.claims); - } - - private void validate() { - if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null - || this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET) != null) { - Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID), "client_id cannot be null"); - } - if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null) { - Assert.isInstanceOf(Instant.class, this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT), - "client_id_issued_at must be of type Instant"); - } - if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT) != null) { - Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET), - "client_secret cannot be null"); - Assert.isInstanceOf(Instant.class, - this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT), - "client_secret_expires_at must be of type Instant"); - } - Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris cannot be null"); - Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), - "redirect_uris must be of type List"); - Assert.notEmpty((List) this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), - "redirect_uris cannot be empty"); - if (this.claims.get(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS) != null) { - Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS), - "post_logout_redirect_uris must be of type List"); - Assert.notEmpty((List) this.claims.get(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS), - "post_logout_redirect_uris cannot be empty"); - } - if (this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES) != null) { - Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), - "grant_types must be of type List"); - Assert.notEmpty((List) this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), - "grant_types cannot be empty"); - } - if (this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES) != null) { - Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), - "response_types must be of type List"); - Assert.notEmpty((List) this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), - "response_types cannot be empty"); - } - if (this.claims.get(OidcClientMetadataClaimNames.SCOPE) != null) { - Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.SCOPE), - "scope must be of type List"); - Assert.notEmpty((List) this.claims.get(OidcClientMetadataClaimNames.SCOPE), "scope cannot be empty"); - } - if (this.claims.get(OidcClientMetadataClaimNames.JWKS_URI) != null) { - validateURL(this.claims.get(OidcClientMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL"); - } - } - - @SuppressWarnings("unchecked") - private void addClaimToClaimList(String name, String value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.computeIfAbsent(name, (k) -> new LinkedList()); - ((List) this.claims.get(name)).add(value); - } - - @SuppressWarnings("unchecked") - private void acceptClaimValues(String name, Consumer> valuesConsumer) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); - this.claims.computeIfAbsent(name, (k) -> new LinkedList()); - List values = (List) this.claims.get(name); - valuesConsumer.accept(values); - } - - private static void validateURL(Object url, String errorMessage) { - if (URL.class.isAssignableFrom(url.getClass())) { - return; - } - - try { - new URI(url.toString()).toURL(); - } - catch (Exception ex) { - throw new IllegalArgumentException(errorMessage, ex); - } - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java deleted file mode 100644 index e298b6bfa..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfiguration.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc; - -import java.io.Serial; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.server.authorization.AbstractOAuth2AuthorizationServerMetadata; -import org.springframework.util.Assert; - -/** - * A representation of an OpenID Provider Configuration Response, which is returned from - * an Issuer's Discovery Endpoint, and contains a set of claims about the OpenID - * Provider's configuration. The claims are defined by the OpenID Connect Discovery 1.0 - * specification. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.0 - * @see AbstractOAuth2AuthorizationServerMetadata - * @see OidcProviderMetadataClaimAccessor - * @see 4.2. - * OpenID Provider Configuration Response - */ -public final class OidcProviderConfiguration extends AbstractOAuth2AuthorizationServerMetadata - implements OidcProviderMetadataClaimAccessor { - - @Serial - private static final long serialVersionUID = -2130128410911549024L; - - private OidcProviderConfiguration(Map claims) { - super(claims); - } - - /** - * Constructs a new {@link Builder} with empty claims. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Constructs a new {@link Builder} with the provided claims. - * @param claims the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder withClaims(Map claims) { - Assert.notEmpty(claims, "claims cannot be empty"); - return new Builder().claims((c) -> c.putAll(claims)); - } - - /** - * Helps configure an {@link OidcProviderConfiguration}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Add this Subject Type to the collection of {@code subject_types_supported} in - * the resulting {@link OidcProviderConfiguration}, REQUIRED. - * @param subjectType the Subject Type that the OpenID Provider supports - * @return the {@link Builder} for further configuration - */ - public Builder subjectType(String subjectType) { - addClaimToClaimList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectType); - return this; - } - - /** - * A {@code Consumer} of the Subject Types(s) allowing the ability to add, - * replace, or remove. - * @param subjectTypesConsumer a {@code Consumer} of the Subject Types(s) - * @return the {@link Builder} for further configuration - */ - public Builder subjectTypes(Consumer> subjectTypesConsumer) { - acceptClaimValues(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectTypesConsumer); - return this; - } - - /** - * Add this {@link JwsAlgorithm JWS} signing algorithm to the collection of - * {@code id_token_signing_alg_values_supported} in the resulting - * {@link OidcProviderConfiguration}, REQUIRED. - * @param signingAlgorithm the {@link JwsAlgorithm JWS} signing algorithm - * supported for the {@link OidcIdToken ID Token} - * @return the {@link Builder} for further configuration - */ - public Builder idTokenSigningAlgorithm(String signingAlgorithm) { - addClaimToClaimList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithm); - return this; - } - - /** - * A {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the - * {@link OidcIdToken ID Token} allowing the ability to add, replace, or remove. - * @param signingAlgorithmsConsumer a {@code Consumer} of the {@link JwsAlgorithm - * JWS} signing algorithms for the {@link OidcIdToken ID Token} - * @return the {@link Builder} for further configuration - */ - public Builder idTokenSigningAlgorithms(Consumer> signingAlgorithmsConsumer) { - acceptClaimValues(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, - signingAlgorithmsConsumer); - return this; - } - - /** - * Use this {@code userinfo_endpoint} in the resulting - * {@link OidcProviderConfiguration}, OPTIONAL. - * @param userInfoEndpoint the {@code URL} of the OpenID Connect 1.0 UserInfo - * Endpoint - * @return the {@link Builder} for further configuration - * @since 0.2.2 - */ - public Builder userInfoEndpoint(String userInfoEndpoint) { - return claim(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, userInfoEndpoint); - } - - /** - * Use this {@code end_session_endpoint} in the resulting - * {@link OidcProviderConfiguration}, OPTIONAL. - * @param endSessionEndpoint the {@code URL} of the OpenID Connect 1.0 End Session - * Endpoint - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder endSessionEndpoint(String endSessionEndpoint) { - return claim(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, endSessionEndpoint); - } - - /** - * Validate the claims and build the {@link OidcProviderConfiguration}. - *

- * The following claims are REQUIRED: {@code issuer}, - * {@code authorization_endpoint}, {@code token_endpoint}, {@code jwks_uri}, - * {@code response_types_supported}, {@code subject_types_supported} and - * {@code id_token_signing_alg_values_supported}. - * @return the {@link OidcProviderConfiguration} - */ - @Override - public OidcProviderConfiguration build() { - validate(); - return new OidcProviderConfiguration(getClaims()); - } - - @Override - protected void validate() { - super.validate(); - Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri cannot be null"); - Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), - "subjectTypes cannot be null"); - Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), - "subjectTypes must be of type List"); - Assert.notEmpty((List) getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), - "subjectTypes cannot be empty"); - Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), - "idTokenSigningAlgorithms cannot be null"); - Assert.isInstanceOf(List.class, - getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), - "idTokenSigningAlgorithms must be of type List"); - Assert.notEmpty( - (List) getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), - "idTokenSigningAlgorithms cannot be empty"); - if (getClaims().get(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT) != null) { - validateURL(getClaims().get(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT), - "userInfoEndpoint must be a valid URL"); - } - if (getClaims().get(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT) != null) { - validateURL(getClaims().get(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT), - "endSessionEndpoint must be a valid URL"); - } - } - - @SuppressWarnings("unchecked") - private void addClaimToClaimList(String name, String value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - getClaims().computeIfAbsent(name, (k) -> new LinkedList()); - ((List) getClaims().get(name)).add(value); - } - - @SuppressWarnings("unchecked") - private void acceptClaimValues(String name, Consumer> valuesConsumer) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); - getClaims().computeIfAbsent(name, (k) -> new LinkedList()); - List values = (List) getClaims().get(name); - valuesConsumer.accept(values); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java deleted file mode 100644 index 906c9ae51..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import java.net.URL; -import java.util.List; - -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimAccessor; - -/** - * A {@link ClaimAccessor} for the "claims" that can be returned in the OpenID Provider - * Configuration Response. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.0 - * @see ClaimAccessor - * @see OAuth2AuthorizationServerMetadataClaimAccessor - * @see OidcProviderMetadataClaimNames - * @see OidcProviderConfiguration - * @see 3. OpenID - * Provider Metadata - */ -public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationServerMetadataClaimAccessor { - - /** - * Returns the Subject Identifier types supported {@code (subject_types_supported)}. - * @return the Subject Identifier types supported - */ - default List getSubjectTypes() { - return getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED); - } - - /** - * Returns the {@link JwsAlgorithm JWS} signing algorithms supported for the - * {@link OidcIdToken ID Token} to encode the claims in a {@link Jwt} - * {@code (id_token_signing_alg_values_supported)}. - * @return the {@link JwsAlgorithm JWS} signing algorithms supported for the - * {@link OidcIdToken ID Token} - */ - default List getIdTokenSigningAlgorithms() { - return getClaimAsStringList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); - } - - /** - * Returns the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint - * {@code (userinfo_endpoint)}. - * @return the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint - * @since 0.2.2 - */ - default URL getUserInfoEndpoint() { - return getClaimAsURL(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT); - } - - /** - * Returns the {@code URL} of the OpenID Connect 1.0 End Session Endpoint - * {@code (end_session_endpoint)}. - * @return the {@code URL} of the OpenID Connect 1.0 End Session Endpoint - * @since 1.1 - */ - default URL getEndSessionEndpoint() { - return getClaimAsURL(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java deleted file mode 100644 index bd951fd59..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimNames.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; - -/** - * The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned - * in the OpenID Provider Configuration Response. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.0 - * @see OAuth2AuthorizationServerMetadataClaimNames - * @see 3. OpenID - * Provider Metadata - */ -public final class OidcProviderMetadataClaimNames extends OAuth2AuthorizationServerMetadataClaimNames { - - /** - * {@code subject_types_supported} - the Subject Identifier types supported - */ - public static final String SUBJECT_TYPES_SUPPORTED = "subject_types_supported"; - - /** - * {@code id_token_signing_alg_values_supported} - the {@link JwsAlgorithm JWS} - * signing algorithms supported for the {@link OidcIdToken ID Token} - */ - public static final String ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = "id_token_signing_alg_values_supported"; - - /** - * {@code userinfo_endpoint} - the {@code URL} of the OpenID Connect 1.0 UserInfo - * Endpoint - * @since 0.2.2 - */ - public static final String USER_INFO_ENDPOINT = "userinfo_endpoint"; - - /** - * {@code end_session_endpoint} - the {@code URL} of the OpenID Connect 1.0 End - * Session Endpoint - * @since 1.1 - */ - public static final String END_SESSION_ENDPOINT = "end_session_endpoint"; - - private OidcProviderMetadataClaimNames() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java deleted file mode 100644 index 7e9ac14e8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; - -/** - * Utility methods for the OpenID Connect 1.0 {@link AuthenticationProvider}'s. - * - * @author Joe Grandja - * @since 0.1.1 - */ -final class OidcAuthenticationProviderUtils { - - private OidcAuthenticationProviderUtils() { - } - - static OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token, - OAuth2TokenContext accessTokenContext) { - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token.getTokenValue(), - token.getIssuedAt(), token.getExpiresAt(), accessTokenContext.getAuthorizedScopes()); - OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient() - .getTokenSettings() - .getAccessTokenFormat(); - builder.token(accessToken, (metadata) -> { - if (token instanceof ClaimAccessor claimAccessor) { - metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims()); - } - metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false); - metadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue()); - }); - - return accessToken; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java deleted file mode 100644 index 8dfd3734e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.authentication; - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; -import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client - * Configuration Endpoint. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Rafal Lewczuk - * @author Dmitriy Dubson - * @since 0.4.0 - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OidcClientRegistrationAuthenticationToken - * @see OidcClientRegistrationAuthenticationProvider - * @see 4. - * Client Configuration Endpoint - */ -public final class OidcClientConfigurationAuthenticationProvider implements AuthenticationProvider { - - static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private Converter clientRegistrationConverter; - - /** - * Constructs an {@code OidcClientConfigurationAuthenticationProvider} using the - * provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - */ - public OidcClientConfigurationAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter(); - } - - /** - * Sets the {@link Converter} used for converting a {@link RegisteredClient} to an - * {@link OidcClientRegistration}. - * @param clientRegistrationConverter the {@link Converter} used for converting a - * {@link RegisteredClient} to an {@link OidcClientRegistration} - * @since 1.2.0 - */ - public void setClientRegistrationConverter( - Converter clientRegistrationConverter) { - Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null"); - this.clientRegistrationConverter = clientRegistrationConverter; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = (OidcClientRegistrationAuthenticationToken) authentication; - - if (!StringUtils.hasText(clientRegistrationAuthentication.getClientId())) { - // This is not a Client Configuration Request. - // Return null to allow OidcClientRegistrationAuthenticationProvider to handle - // it. - return null; - } - - // Validate the "registration" access token - AbstractOAuth2TokenAuthenticationToken accessTokenAuthentication = null; - if (AbstractOAuth2TokenAuthenticationToken.class - .isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) { - accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken) clientRegistrationAuthentication - .getPrincipal(); - } - if (accessTokenAuthentication == null || !accessTokenAuthentication.isAuthenticated()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessTokenValue, - OAuth2TokenType.ACCESS_TOKEN); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with access token"); - } - - OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); - if (!authorizedAccessToken.isActive()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE)); - - return findRegistration(clientRegistrationAuthentication, authorization); - } - - @Override - public boolean supports(Class authentication) { - return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication); - } - - private OidcClientRegistrationAuthenticationToken findRegistration( - OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, - OAuth2Authorization authorization) { - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationAuthentication.getClientId()); - if (registeredClient == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); - } - - if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client configuration request parameters"); - } - - OidcClientRegistration clientRegistration = this.clientRegistrationConverter.convert(registeredClient); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated client configuration request"); - } - - return new OidcClientRegistrationAuthenticationToken( - (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration); - } - - @SuppressWarnings("unchecked") - private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, - Set requiredScope) { - Collection authorizedScope = Collections.emptySet(); - if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { - authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); - } - if (!authorizedScope.containsAll(requiredScope)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - } - else if (authorizedScope.size() != requiredScope.size()) { - // Restrict the access token to only contain the required scope - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java deleted file mode 100644 index fcfe0375d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; -import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; -import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client - * Registration Endpoint. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Rafal Lewczuk - * @author Dmitriy Dubson - * @since 0.1.1 - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see OAuth2TokenGenerator - * @see OidcClientRegistrationAuthenticationToken - * @see OidcClientConfigurationAuthenticationProvider - * @see PasswordEncoder - * @see 3. - * Client Registration Endpoint - */ -public final class OidcClientRegistrationAuthenticationProvider implements AuthenticationProvider { - - private static final String ERROR_URI = "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError"; - - private static final String DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE = "client.create"; - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final OAuth2TokenGenerator tokenGenerator; - - private Converter clientRegistrationConverter; - - private Converter registeredClientConverter; - - private PasswordEncoder passwordEncoder; - - /** - * Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the - * provided parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param tokenGenerator the token generator - * @since 0.2.3 - */ - public OidcClientRegistrationAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, - OAuth2TokenGenerator tokenGenerator) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.tokenGenerator = tokenGenerator; - this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter(); - this.registeredClientConverter = new OidcClientRegistrationRegisteredClientConverter(); - this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = (OidcClientRegistrationAuthenticationToken) authentication; - - if (clientRegistrationAuthentication.getClientRegistration() == null) { - // This is not a Client Registration Request. - // Return null to allow OidcClientConfigurationAuthenticationProvider to - // handle it. - return null; - } - - // Validate the "initial" access token - AbstractOAuth2TokenAuthenticationToken accessTokenAuthentication = null; - if (AbstractOAuth2TokenAuthenticationToken.class - .isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) { - accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken) clientRegistrationAuthentication - .getPrincipal(); - } - if (accessTokenAuthentication == null || !accessTokenAuthentication.isAuthenticated()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessTokenValue, - OAuth2TokenType.ACCESS_TOKEN); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with initial access token"); - } - - OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); - if (!authorizedAccessToken.isActive()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE)); - - return registerClient(clientRegistrationAuthentication, authorization); - } - - @Override - public boolean supports(Class authentication) { - return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link Converter} used for converting an {@link OidcClientRegistration} to - * a {@link RegisteredClient}. - * @param registeredClientConverter the {@link Converter} used for converting an - * {@link OidcClientRegistration} to a {@link RegisteredClient} - * @since 0.4.0 - */ - public void setRegisteredClientConverter( - Converter registeredClientConverter) { - Assert.notNull(registeredClientConverter, "registeredClientConverter cannot be null"); - this.registeredClientConverter = registeredClientConverter; - } - - /** - * Sets the {@link Converter} used for converting a {@link RegisteredClient} to an - * {@link OidcClientRegistration}. - * @param clientRegistrationConverter the {@link Converter} used for converting a - * {@link RegisteredClient} to an {@link OidcClientRegistration} - * @since 1.2.0 - */ - public void setClientRegistrationConverter( - Converter clientRegistrationConverter) { - Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null"); - this.clientRegistrationConverter = clientRegistrationConverter; - } - - /** - * Sets the {@link PasswordEncoder} used to encode the - * {@link RegisteredClient#getClientSecret() client secret}. If not set, the client - * secret will be encoded using - * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}. - * @param passwordEncoder the {@link PasswordEncoder} used to encode the client secret - * @since 1.1.0 - */ - public void setPasswordEncoder(PasswordEncoder passwordEncoder) { - Assert.notNull(passwordEncoder, "passwordEncoder cannot be null"); - this.passwordEncoder = passwordEncoder; - } - - private OidcClientRegistrationAuthenticationToken registerClient( - OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, - OAuth2Authorization authorization) { - - if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) { - throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI, - OidcClientMetadataClaimNames.REDIRECT_URIS); - } - - if (!isValidRedirectUris( - clientRegistrationAuthentication.getClientRegistration().getPostLogoutRedirectUris())) { - throwInvalidClientRegistration("invalid_client_metadata", - OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); - } - - if (!isValidTokenEndpointAuthenticationMethod(clientRegistrationAuthentication.getClientRegistration())) { - throwInvalidClientRegistration("invalid_client_metadata", - OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated client registration request parameters"); - } - - RegisteredClient registeredClient = this.registeredClientConverter - .convert(clientRegistrationAuthentication.getClientRegistration()); - - if (StringUtils.hasText(registeredClient.getClientSecret())) { - // Encode the client secret - RegisteredClient updatedRegisteredClient = RegisteredClient.from(registeredClient) - .clientSecret(this.passwordEncoder.encode(registeredClient.getClientSecret())) - .build(); - this.registeredClientRepository.save(updatedRegisteredClient); - if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue() - .equals(clientRegistrationAuthentication.getClientRegistration() - .getTokenEndpointAuthenticationMethod())) { - // gh-1344 Return the hashed client_secret - registeredClient = updatedRegisteredClient; - } - } - else { - this.registeredClientRepository.save(registeredClient); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved registered client"); - } - - OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient); - - // Invalidate the "initial" access token as it can only be used once - OAuth2Authorization.Builder builder = OAuth2Authorization.from(authorization) - .invalidate(authorization.getAccessToken().getToken()); - if (authorization.getRefreshToken() != null) { - builder.invalidate(authorization.getRefreshToken().getToken()); - } - authorization = builder.build(); - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization with invalidated initial access token"); - } - - Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient) - .getClaims(); - OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims) - .registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue()) - .build(); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated client registration request"); - } - - return new OidcClientRegistrationAuthenticationToken( - (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration); - } - - private OAuth2Authorization registerAccessToken(RegisteredClient registeredClient) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - registeredClient.getClientAuthenticationMethods().iterator().next(), - registeredClient.getClientSecret()); - - Set authorizedScopes = new HashSet<>(); - authorizedScopes - .add(OidcClientConfigurationAuthenticationProvider.DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE); - authorizedScopes = Collections.unmodifiableSet(authorizedScopes); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(clientPrincipal) - .authorizationServerContext(AuthorizationServerContextHolder.getContext()) - .authorizedScopes(authorizedScopes) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - // @formatter:on - - OAuth2Token registrationAccessToken = this.tokenGenerator.generate(tokenContext); - if (registrationAccessToken == null) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "The token generator failed to generate the registration access token.", ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Generated registration access token"); - } - - // @formatter:off - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizedScopes(authorizedScopes); - // @formatter:on - - OidcAuthenticationProviderUtils.accessToken(authorizationBuilder, registrationAccessToken, tokenContext); - - OAuth2Authorization authorization = authorizationBuilder.build(); - - this.authorizationService.save(authorization); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Saved authorization with registration access token"); - } - - return authorization; - } - - @SuppressWarnings("unchecked") - private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, - Set requiredScope) { - Collection authorizedScope = Collections.emptySet(); - if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { - authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); - } - if (!authorizedScope.containsAll(requiredScope)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - } - else if (authorizedScope.size() != requiredScope.size()) { - // Restrict the access token to only contain the required scope - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - } - - private static boolean isValidRedirectUris(List redirectUris) { - if (CollectionUtils.isEmpty(redirectUris)) { - return true; - } - - for (String redirectUri : redirectUris) { - try { - URI validRedirectUri = new URI(redirectUri); - if (validRedirectUri.getFragment() != null) { - return false; - } - } - catch (URISyntaxException ex) { - return false; - } - } - - return true; - } - - private static boolean isValidTokenEndpointAuthenticationMethod(OidcClientRegistration clientRegistration) { - String authenticationMethod = clientRegistration.getTokenEndpointAuthenticationMethod(); - String authenticationSigningAlgorithm = clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm(); - - if (!ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(authenticationMethod) - && !ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(authenticationMethod)) { - return !StringUtils.hasText(authenticationSigningAlgorithm); - } - - if ("none".equals(authenticationSigningAlgorithm)) { - return false; - } - - if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(authenticationMethod)) { - return clientRegistration.getJwkSetUrl() != null && (!StringUtils.hasText(authenticationSigningAlgorithm) - || SignatureAlgorithm.from(authenticationSigningAlgorithm) != null); - } - else { - // client_secret_jwt - return !StringUtils.hasText(authenticationSigningAlgorithm) - || MacAlgorithm.from(authenticationSigningAlgorithm) != null; - } - } - - private static void throwInvalidClientRegistration(String errorCode, String fieldName) { - OAuth2Error error = new OAuth2Error(errorCode, "Invalid Client Registration: " + fieldName, ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java deleted file mode 100644 index ec841d6c8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.io.Serial; -import java.util.Collections; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OpenID Connect 1.0 Dynamic Client - * Registration (and Configuration) Endpoint. - * - * @author Joe Grandja - * @author Ovidiu Popa - * @since 0.1.1 - * @see AbstractAuthenticationToken - * @see OidcClientRegistration - * @see OidcClientRegistrationAuthenticationProvider - * @see OidcClientConfigurationAuthenticationProvider - */ -public class OidcClientRegistrationAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -6198261907690781217L; - - private final Authentication principal; - - private final OidcClientRegistration clientRegistration; - - private final String clientId; - - /** - * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided - * parameters. - * @param principal the authenticated principal - * @param clientRegistration the client registration - */ - public OidcClientRegistrationAuthenticationToken(Authentication principal, - OidcClientRegistration clientRegistration) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - Assert.notNull(clientRegistration, "clientRegistration cannot be null"); - this.principal = principal; - this.clientRegistration = clientRegistration; - this.clientId = null; - setAuthenticated(principal.isAuthenticated()); - } - - /** - * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided - * parameters. - * @param principal the authenticated principal - * @param clientId the client identifier - * @since 0.2.1 - */ - public OidcClientRegistrationAuthenticationToken(Authentication principal, String clientId) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(clientId, "clientId cannot be empty"); - this.principal = principal; - this.clientRegistration = null; - this.clientId = clientId; - setAuthenticated(principal.isAuthenticated()); - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the client registration. - * @return the client registration - */ - public OidcClientRegistration getClientRegistration() { - return this.clientRegistration; - } - - /** - * Returns the client identifier. - * @return the client identifier - * @since 0.2.1 - */ - @Nullable - public String getClientId() { - return this.clientId; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java deleted file mode 100644 index 08b810ba2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationContext; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OidcLogoutAuthenticationToken} and additional information and is used when - * validating the OpenID Connect RP-Initiated Logout Request parameters. - * - * @author Daniel Garnier-Moiroux - * @since 1.4 - * @see OAuth2AuthenticationContext - * @see OidcLogoutAuthenticationToken - * @see OidcLogoutAuthenticationProvider#setAuthenticationValidator(Consumer) - */ -public final class OidcLogoutAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OidcLogoutAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OidcLogoutAuthenticationToken}. - * @param authentication the {@link OidcLogoutAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OidcLogoutAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OidcLogoutAuthenticationContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OidcLogoutAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link Builder} for further configuration - */ - public Builder registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Builds a new {@link OidcLogoutAuthenticationContext}. - * @return the {@link OidcLogoutAuthenticationContext} - */ - @Override - public OidcLogoutAuthenticationContext build() { - Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); - return new OidcLogoutAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java deleted file mode 100644 index 65dde288c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.util.Base64; -import java.util.List; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 RP-Initiated - * Logout Endpoint. - * - * @author Joe Grandja - * @since 1.1 - * @see RegisteredClientRepository - * @see OAuth2AuthorizationService - * @see SessionRegistry - * @see 2. - * RP-Initiated Logout - */ -public final class OidcLogoutAuthenticationProvider implements AuthenticationProvider { - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private final Log logger = LogFactory.getLog(getClass()); - - private final RegisteredClientRepository registeredClientRepository; - - private final OAuth2AuthorizationService authorizationService; - - private final SessionRegistry sessionRegistry; - - private Consumer authenticationValidator = new OidcLogoutAuthenticationValidator(); - - /** - * Constructs an {@code OidcLogoutAuthenticationProvider} using the provided - * parameters. - * @param registeredClientRepository the repository of registered clients - * @param authorizationService the authorization service - * @param sessionRegistry the {@link SessionRegistry} used to track OpenID Connect - * sessions - */ - public OidcLogoutAuthenticationProvider(RegisteredClientRepository registeredClientRepository, - OAuth2AuthorizationService authorizationService, SessionRegistry sessionRegistry) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - Assert.notNull(authorizationService, "authorizationService cannot be null"); - Assert.notNull(sessionRegistry, "sessionRegistry cannot be null"); - this.registeredClientRepository = registeredClientRepository; - this.authorizationService = authorizationService; - this.sessionRegistry = sessionRegistry; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication; - - OAuth2Authorization authorization = this.authorizationService - .findByToken(oidcLogoutAuthentication.getIdTokenHint(), ID_TOKEN_TOKEN_TYPE); - if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with ID Token"); - } - - OAuth2Authorization.Token authorizedIdToken = authorization.getToken(OidcIdToken.class); - if (authorizedIdToken.isInvalidated() || authorizedIdToken.isBeforeUse()) { - // Expired ID Token should be accepted - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); - } - - RegisteredClient registeredClient = this.registeredClientRepository - .findById(authorization.getRegisteredClientId()); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved registered client"); - } - - OidcIdToken idToken = authorizedIdToken.getToken(); - - // Validate client identity - List audClaim = idToken.getAudience(); - if (CollectionUtils.isEmpty(audClaim) || !audClaim.contains(registeredClient.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.AUD); - } - if (StringUtils.hasText(oidcLogoutAuthentication.getClientId()) - && !oidcLogoutAuthentication.getClientId().equals(registeredClient.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); - } - - OidcLogoutAuthenticationContext context = OidcLogoutAuthenticationContext.with(oidcLogoutAuthentication) - .registeredClient(registeredClient) - .build(); - this.authenticationValidator.accept(context); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated logout request parameters"); - } - - // Validate user identity - if (oidcLogoutAuthentication.isPrincipalAuthenticated()) { - Authentication currentUserPrincipal = (Authentication) oidcLogoutAuthentication.getPrincipal(); - Authentication authorizedUserPrincipal = authorization.getAttribute(Principal.class.getName()); - if (!StringUtils.hasText(idToken.getSubject()) - || !currentUserPrincipal.getName().equals(authorizedUserPrincipal.getName())) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.SUB); - } - - // Check for active session - if (StringUtils.hasText(oidcLogoutAuthentication.getSessionId())) { - SessionInformation sessionInformation = findSessionInformation(currentUserPrincipal, - oidcLogoutAuthentication.getSessionId()); - if (sessionInformation != null) { - String sessionIdHash; - try { - sessionIdHash = createHash(sessionInformation.getSessionId()); - } - catch (NoSuchAlgorithmException ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Failed to compute hash for Session ID.", null); - throw new OAuth2AuthenticationException(error); - } - - String sidClaim = idToken.getClaim("sid"); - if (!StringUtils.hasText(sidClaim) || !sidClaim.equals(sessionIdHash)) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid"); - } - } - } - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated logout request"); - } - - return new OidcLogoutAuthenticationToken(idToken, (Authentication) oidcLogoutAuthentication.getPrincipal(), - oidcLogoutAuthentication.getSessionId(), oidcLogoutAuthentication.getClientId(), - oidcLogoutAuthentication.getPostLogoutRedirectUri(), oidcLogoutAuthentication.getState()); - } - - @Override - public boolean supports(Class authentication) { - return OidcLogoutAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OidcLogoutAuthenticationContext} and is responsible for validating specific - * OpenID Connect RP-Initiated Logout Request parameters associated in the - * {@link OidcLogoutAuthenticationToken}. The default authentication validator is - * {@link OidcLogoutAuthenticationValidator}. - * - *

- * NOTE: The authentication validator MUST throw - * {@link OAuth2AuthenticationException} if validation fails. - * @param authenticationValidator the {@code Consumer} providing access to the - * {@link OidcLogoutAuthenticationContext} and is responsible for validating specific - * OpenID Connect RP-Initiated Logout Request parameters - * @since 1.4 - */ - public void setAuthenticationValidator(Consumer authenticationValidator) { - Assert.notNull(authenticationValidator, "authenticationValidator cannot be null"); - this.authenticationValidator = authenticationValidator; - } - - private SessionInformation findSessionInformation(Authentication principal, String sessionId) { - List sessions = this.sessionRegistry.getAllSessions(principal.getPrincipal(), true); - SessionInformation sessionInformation = null; - if (!CollectionUtils.isEmpty(sessions)) { - for (SessionInformation session : sessions) { - if (session.getSessionId().equals(sessionId)) { - sessionInformation = session; - break; - } - } - } - return sessionInformation; - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OpenID Connect 1.0 Logout Request Parameter: " + parameterName, - "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - throw new OAuth2AuthenticationException(error); - } - - private static String createHash(String value) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java deleted file mode 100644 index 77b1ae7b1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.io.Serial; -import java.util.Collections; - -import org.springframework.lang.Nullable; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OpenID Connect 1.0 RP-Initiated - * Logout Endpoint. - * - * @author Joe Grandja - * @since 1.1 - * @see AbstractAuthenticationToken - * @see OidcLogoutAuthenticationProvider - */ -public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = 4001993612314913888L; - - private final String idTokenHint; - - private final OidcIdToken idToken; - - private final Authentication principal; - - private final String sessionId; - - private final String clientId; - - private final String postLogoutRedirectUri; - - private final String state; - - /** - * Constructs an {@code OidcLogoutAuthenticationToken} using the provided parameters. - * @param idTokenHint the ID Token previously issued by the Provider to the Client and - * used as a hint about the End-User's current authenticated session with the Client - * @param principal the authenticated principal representing the End-User - * @param sessionId the End-User's current authenticated session identifier with the - * Provider - * @param clientId the client identifier the ID Token was issued to - * @param postLogoutRedirectUri the URI which the Client is requesting that the - * End-User's User Agent be redirected to after a logout has been performed - * @param state the opaque value used by the Client to maintain state between the - * logout request and the callback to the {@code postLogoutRedirectUri} - */ - public OidcLogoutAuthenticationToken(String idTokenHint, Authentication principal, @Nullable String sessionId, - @Nullable String clientId, @Nullable String postLogoutRedirectUri, @Nullable String state) { - super(Collections.emptyList()); - Assert.hasText(idTokenHint, "idTokenHint cannot be empty"); - Assert.notNull(principal, "principal cannot be null"); - this.idTokenHint = idTokenHint; - this.idToken = null; - this.principal = principal; - this.sessionId = sessionId; - this.clientId = clientId; - this.postLogoutRedirectUri = postLogoutRedirectUri; - this.state = state; - setAuthenticated(false); - } - - /** - * Constructs an {@code OidcLogoutAuthenticationToken} using the provided parameters. - * @param idToken the ID Token previously issued by the Provider to the Client - * @param principal the authenticated principal representing the End-User - * @param sessionId the End-User's current authenticated session identifier with the - * Provider - * @param clientId the client identifier the ID Token was issued to - * @param postLogoutRedirectUri the URI which the Client is requesting that the - * End-User's User Agent be redirected to after a logout has been performed - * @param state the opaque value used by the Client to maintain state between the - * logout request and the callback to the {@code postLogoutRedirectUri} - */ - public OidcLogoutAuthenticationToken(OidcIdToken idToken, Authentication principal, @Nullable String sessionId, - @Nullable String clientId, @Nullable String postLogoutRedirectUri, @Nullable String state) { - super(Collections.emptyList()); - Assert.notNull(idToken, "idToken cannot be null"); - Assert.notNull(principal, "principal cannot be null"); - this.idTokenHint = idToken.getTokenValue(); - this.idToken = idToken; - this.principal = principal; - this.sessionId = sessionId; - this.clientId = clientId; - this.postLogoutRedirectUri = postLogoutRedirectUri; - this.state = state; - setAuthenticated(true); - } - - /** - * Returns the authenticated principal representing the End-User. - * @return the authenticated principal representing the End-User - */ - @Override - public Object getPrincipal() { - return this.principal; - } - - /** - * Returns {@code true} if {@link #getPrincipal()} is authenticated, {@code false} - * otherwise. - * @return {@code true} if {@link #getPrincipal()} is authenticated, {@code false} - * otherwise - */ - public boolean isPrincipalAuthenticated() { - return !AnonymousAuthenticationToken.class.isAssignableFrom(this.principal.getClass()) - && this.principal.isAuthenticated(); - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the ID Token previously issued by the Provider to the Client and used as a - * hint about the End-User's current authenticated session with the Client. - * @return the ID Token previously issued by the Provider to the Client - */ - public String getIdTokenHint() { - return this.idTokenHint; - } - - /** - * Returns the ID Token previously issued by the Provider to the Client. - * @return the ID Token previously issued by the Provider to the Client - */ - @Nullable - public OidcIdToken getIdToken() { - return this.idToken; - } - - /** - * Returns the End-User's current authenticated session identifier with the Provider. - * @return the End-User's current authenticated session identifier with the Provider - */ - @Nullable - public String getSessionId() { - return this.sessionId; - } - - /** - * Returns the client identifier the ID Token was issued to. - * @return the client identifier - */ - @Nullable - public String getClientId() { - return this.clientId; - } - - /** - * Returns the URI which the Client is requesting that the End-User's User Agent be - * redirected to after a logout has been performed. - * @return the URI which the Client is requesting that the End-User's User Agent be - * redirected to after a logout has been performed - */ - @Nullable - public String getPostLogoutRedirectUri() { - return this.postLogoutRedirectUri; - } - - /** - * Returns the opaque value used by the Client to maintain state between the logout - * request and the callback to the {@link #getPostLogoutRedirectUri()}. - * @return the opaque value used by the Client to maintain state between the logout - * request and the callback to the {@link #getPostLogoutRedirectUri()} - */ - @Nullable - public String getState() { - return this.state; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationValidator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationValidator.java deleted file mode 100644 index 55e32ee1f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationValidator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.util.function.Consumer; - -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.util.StringUtils; - -/** - * A {@code Consumer} providing access to the {@link OidcLogoutAuthenticationContext} - * containing an {@link OidcLogoutAuthenticationToken} and is the default - * {@link OidcLogoutAuthenticationProvider#setAuthenticationValidator(Consumer) - * authentication validator} used for validating specific OpenID Connect RP-Initiated - * Logout Request parameters. - * - *

- * The default implementation validates - * {@link OidcLogoutAuthenticationToken#getPostLogoutRedirectUri()}. If validation fails, - * an {@link OAuth2AuthenticationException} is thrown. - * - * @author Daniel Garnier-Moiroux - * @since 1.4 - * @see OidcLogoutAuthenticationContext - * @see OidcLogoutAuthenticationToken - * @see OidcLogoutAuthenticationProvider#setAuthenticationValidator(Consumer) - */ -public final class OidcLogoutAuthenticationValidator implements Consumer { - - /** - * The default validator for - * {@link OidcLogoutAuthenticationToken#getPostLogoutRedirectUri()}. - */ - public static final Consumer DEFAULT_POST_LOGOUT_REDIRECT_URI_VALIDATOR = OidcLogoutAuthenticationValidator::validatePostLogoutRedirectUri; - - private final Consumer authenticationValidator = DEFAULT_POST_LOGOUT_REDIRECT_URI_VALIDATOR; - - @Override - public void accept(OidcLogoutAuthenticationContext authenticationContext) { - this.authenticationValidator.accept(authenticationContext); - } - - private static void validatePostLogoutRedirectUri(OidcLogoutAuthenticationContext authenticationContext) { - OidcLogoutAuthenticationToken oidcLogoutAuthentication = authenticationContext.getAuthentication(); - RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); - if (StringUtils.hasText(oidcLogoutAuthentication.getPostLogoutRedirectUri()) - && !registeredClient.getPostLogoutRedirectUris() - .contains(oidcLogoutAuthentication.getPostLogoutRedirectUri())) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Connect 1.0 Logout Request Parameter: post_logout_redirect_uri", - "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - throw new OAuth2AuthenticationException(error); - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java deleted file mode 100644 index 744caff8b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationContext; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2AuthenticationContext} that holds an - * {@link OidcUserInfoAuthenticationToken} and additional information and is used when - * mapping claims to an instance of {@link OidcUserInfo}. - * - * @author Joe Grandja - * @since 0.2.1 - * @see OAuth2AuthenticationContext - * @see OidcUserInfo - * @see OidcUserInfoAuthenticationProvider#setUserInfoMapper(Function) - */ -public final class OidcUserInfoAuthenticationContext implements OAuth2AuthenticationContext { - - private final Map context; - - private OidcUserInfoAuthenticationContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link OAuth2AccessToken OAuth 2.0 Access Token}. - * @return the {@link OAuth2AccessToken} - */ - public OAuth2AccessToken getAccessToken() { - return get(OAuth2AccessToken.class); - } - - /** - * Returns the {@link OAuth2Authorization authorization}. - * @return the {@link OAuth2Authorization} - */ - public OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); - } - - /** - * Constructs a new {@link Builder} with the provided - * {@link OidcUserInfoAuthenticationToken}. - * @param authentication the {@link OidcUserInfoAuthenticationToken} - * @return the {@link Builder} - */ - public static Builder with(OidcUserInfoAuthenticationToken authentication) { - return new Builder(authentication); - } - - /** - * A builder for {@link OidcUserInfoAuthenticationContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OidcUserInfoAuthenticationToken authentication) { - super(authentication); - } - - /** - * Sets the {@link OAuth2AccessToken OAuth 2.0 Access Token}. - * @param accessToken the {@link OAuth2AccessToken} - * @return the {@link Builder} for further configuration - */ - public Builder accessToken(OAuth2AccessToken accessToken) { - return put(OAuth2AccessToken.class, accessToken); - } - - /** - * Sets the {@link OAuth2Authorization authorization}. - * @param authorization the {@link OAuth2Authorization} - * @return the {@link Builder} for further configuration - */ - public Builder authorization(OAuth2Authorization authorization) { - return put(OAuth2Authorization.class, authorization); - } - - /** - * Builds a new {@link OidcUserInfoAuthenticationContext}. - * @return the {@link OidcUserInfoAuthenticationContext} - */ - @Override - public OidcUserInfoAuthenticationContext build() { - Assert.notNull(get(OAuth2AccessToken.class), "accessToken cannot be null"); - Assert.notNull(get(OAuth2Authorization.class), "authorization cannot be null"); - return new OidcUserInfoAuthenticationContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java deleted file mode 100644 index 1e900a82a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.oidc.authentication; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 UserInfo - * Endpoint. - * - * @author Steve Riesenberg - * @since 0.2.1 - * @see OAuth2AuthorizationService - * @see 5.3. - * UserInfo Endpoint - */ -public final class OidcUserInfoAuthenticationProvider implements AuthenticationProvider { - - private final Log logger = LogFactory.getLog(getClass()); - - private final OAuth2AuthorizationService authorizationService; - - private Function userInfoMapper = new DefaultOidcUserInfoMapper(); - - /** - * Constructs an {@code OidcUserInfoAuthenticationProvider} using the provided - * parameters. - * @param authorizationService the authorization service - */ - public OidcUserInfoAuthenticationProvider(OAuth2AuthorizationService authorizationService) { - Assert.notNull(authorizationService, "authorizationService cannot be null"); - this.authorizationService = authorizationService; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OidcUserInfoAuthenticationToken userInfoAuthentication = (OidcUserInfoAuthenticationToken) authentication; - - AbstractOAuth2TokenAuthenticationToken accessTokenAuthentication = null; - if (AbstractOAuth2TokenAuthenticationToken.class - .isAssignableFrom(userInfoAuthentication.getPrincipal().getClass())) { - accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken) userInfoAuthentication - .getPrincipal(); - } - if (accessTokenAuthentication == null || !accessTokenAuthentication.isAuthenticated()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - String accessTokenValue = accessTokenAuthentication.getToken().getTokenValue(); - - OAuth2Authorization authorization = this.authorizationService.findByToken(accessTokenValue, - OAuth2TokenType.ACCESS_TOKEN); - if (authorization == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Retrieved authorization with access token"); - } - - OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); - if (!authorizedAccessToken.isActive()) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - if (!authorizedAccessToken.getToken().getScopes().contains(OidcScopes.OPENID)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - } - - OAuth2Authorization.Token idToken = authorization.getToken(OidcIdToken.class); - if (idToken == null) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Validated user info request"); - } - - OidcUserInfoAuthenticationContext authenticationContext = OidcUserInfoAuthenticationContext - .with(userInfoAuthentication) - .accessToken(authorizedAccessToken.getToken()) - .authorization(authorization) - .build(); - OidcUserInfo userInfo = this.userInfoMapper.apply(authenticationContext); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authenticated user info request"); - } - - return new OidcUserInfoAuthenticationToken(accessTokenAuthentication, userInfo); - } - - @Override - public boolean supports(Class authentication) { - return OidcUserInfoAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Sets the {@link Function} used to extract claims from - * {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo} - * for the UserInfo response. - * - *

- * The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the - * {@link OidcUserInfoAuthenticationToken}, as well as, the following context - * attributes: - *

    - *
  • {@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the - * bearer token used to make the request.
  • - *
  • {@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the - * {@link OidcIdToken} and {@link OAuth2AccessToken} associated with the bearer token - * used to make the request.
  • - *
- * @param userInfoMapper the {@link Function} used to extract claims from - * {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo} - */ - public void setUserInfoMapper(Function userInfoMapper) { - Assert.notNull(userInfoMapper, "userInfoMapper cannot be null"); - this.userInfoMapper = userInfoMapper; - } - - private static final class DefaultOidcUserInfoMapper - implements Function { - - // @formatter:off - private static final List EMAIL_CLAIMS = Arrays.asList( - StandardClaimNames.EMAIL, - StandardClaimNames.EMAIL_VERIFIED - ); - private static final List PHONE_CLAIMS = Arrays.asList( - StandardClaimNames.PHONE_NUMBER, - StandardClaimNames.PHONE_NUMBER_VERIFIED - ); - private static final List PROFILE_CLAIMS = Arrays.asList( - StandardClaimNames.NAME, - StandardClaimNames.FAMILY_NAME, - StandardClaimNames.GIVEN_NAME, - StandardClaimNames.MIDDLE_NAME, - StandardClaimNames.NICKNAME, - StandardClaimNames.PREFERRED_USERNAME, - StandardClaimNames.PROFILE, - StandardClaimNames.PICTURE, - StandardClaimNames.WEBSITE, - StandardClaimNames.GENDER, - StandardClaimNames.BIRTHDATE, - StandardClaimNames.ZONEINFO, - StandardClaimNames.LOCALE, - StandardClaimNames.UPDATED_AT - ); - // @formatter:on - - @Override - public OidcUserInfo apply(OidcUserInfoAuthenticationContext authenticationContext) { - OAuth2Authorization authorization = authenticationContext.getAuthorization(); - OidcIdToken idToken = authorization.getToken(OidcIdToken.class).getToken(); - OAuth2AccessToken accessToken = authenticationContext.getAccessToken(); - Map scopeRequestedClaims = getClaimsRequestedByScope(idToken.getClaims(), - accessToken.getScopes()); - - return new OidcUserInfo(scopeRequestedClaims); - } - - private static Map getClaimsRequestedByScope(Map claims, - Set requestedScopes) { - Set scopeRequestedClaimNames = new HashSet<>(32); - scopeRequestedClaimNames.add(StandardClaimNames.SUB); - - if (requestedScopes.contains(OidcScopes.ADDRESS)) { - scopeRequestedClaimNames.add(StandardClaimNames.ADDRESS); - } - if (requestedScopes.contains(OidcScopes.EMAIL)) { - scopeRequestedClaimNames.addAll(EMAIL_CLAIMS); - } - if (requestedScopes.contains(OidcScopes.PHONE)) { - scopeRequestedClaimNames.addAll(PHONE_CLAIMS); - } - if (requestedScopes.contains(OidcScopes.PROFILE)) { - scopeRequestedClaimNames.addAll(PROFILE_CLAIMS); - } - - Map requestedClaims = new HashMap<>(claims); - requestedClaims.keySet().removeIf((claimName) -> !scopeRequestedClaimNames.contains(claimName)); - - return requestedClaims; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java deleted file mode 100644 index cba4aee6d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.io.Serial; -import java.util.Collections; - -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.util.Assert; - -/** - * An {@link Authentication} implementation used for OpenID Connect 1.0 UserInfo Endpoint. - * - * @author Steve Riesenberg - * @since 0.2.1 - * @see AbstractAuthenticationToken - * @see OidcUserInfo - * @see OidcUserInfoAuthenticationProvider - */ -public class OidcUserInfoAuthenticationToken extends AbstractAuthenticationToken { - - @Serial - private static final long serialVersionUID = -3463488286180103730L; - - private final Authentication principal; - - private final OidcUserInfo userInfo; - - /** - * Constructs an {@code OidcUserInfoAuthenticationToken} using the provided - * parameters. - * @param principal the principal - */ - public OidcUserInfoAuthenticationToken(Authentication principal) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - this.principal = principal; - this.userInfo = null; - setAuthenticated(false); - } - - /** - * Constructs an {@code OidcUserInfoAuthenticationToken} using the provided - * parameters. - * @param principal the authenticated principal - * @param userInfo the UserInfo claims - */ - public OidcUserInfoAuthenticationToken(Authentication principal, OidcUserInfo userInfo) { - super(Collections.emptyList()); - Assert.notNull(principal, "principal cannot be null"); - Assert.notNull(userInfo, "userInfo cannot be null"); - this.principal = principal; - this.userInfo = userInfo; - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return ""; - } - - /** - * Returns the UserInfo claims. - * @return the UserInfo claims - */ - public OidcUserInfo getUserInfo() { - return this.userInfo; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java deleted file mode 100644 index 2253bb583..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.converter; - -import java.time.Instant; -import java.util.Base64; -import java.util.UUID; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.util.CollectionUtils; - -/** - * A {@link Converter} that converts the provided {@link OidcClientRegistration} to a - * {@link RegisteredClient}. - * - * @author Joe Grandja - * @author Dmitriy Dubson - * @since 1.2.0 - */ -public final class OidcClientRegistrationRegisteredClientConverter - implements Converter { - - private static final StringKeyGenerator CLIENT_ID_GENERATOR = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 32); - - private static final StringKeyGenerator CLIENT_SECRET_GENERATOR = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 48); - - @Override - public RegisteredClient convert(OidcClientRegistration clientRegistration) { - // @formatter:off - RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId(CLIENT_ID_GENERATOR.generateKey()) - .clientIdIssuedAt(Instant.now()) - .clientName(clientRegistration.getClientName()); - - if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - builder - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .clientSecret(CLIENT_SECRET_GENERATOR.generateKey()); - } - else if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - builder - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSecret(CLIENT_SECRET_GENERATOR.generateKey()); - } - else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - builder.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT); - } - else { - builder - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .clientSecret(CLIENT_SECRET_GENERATOR.generateKey()); - } - - builder.redirectUris((redirectUris) -> - redirectUris.addAll(clientRegistration.getRedirectUris())); - - if (!CollectionUtils.isEmpty(clientRegistration.getPostLogoutRedirectUris())) { - builder.postLogoutRedirectUris((postLogoutRedirectUris) -> - postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris())); - } - - if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) { - builder.authorizationGrantTypes((authorizationGrantTypes) -> - clientRegistration.getGrantTypes().forEach((grantType) -> - authorizationGrantTypes.add(new AuthorizationGrantType(grantType)))); - } - else { - builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); - } - if (CollectionUtils.isEmpty(clientRegistration.getResponseTypes()) || - clientRegistration.getResponseTypes().contains(OAuth2AuthorizationResponseType.CODE.getValue())) { - builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE); - } - - if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) { - builder.scopes((scopes) -> - scopes.addAll(clientRegistration.getScopes())); - } - - ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder() - .requireProofKey(true) - .requireAuthorizationConsent(true); - - if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()); - if (macAlgorithm == null) { - macAlgorithm = MacAlgorithm.HS256; - } - clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm); - } - else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()); - if (signatureAlgorithm == null) { - signatureAlgorithm = SignatureAlgorithm.RS256; - } - clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm); - clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString()); - } - - builder - .clientSettings(clientSettingsBuilder.build()) - .tokenSettings(TokenSettings.builder() - .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256) - .build()); - - return builder.build(); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java deleted file mode 100644 index 7cd39a5ca..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.converter; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.util.CollectionUtils; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@link Converter} that converts the provided {@link RegisteredClient} to an - * {@link OidcClientRegistration}. - * - * @author Joe Grandja - * @since 1.2.0 - */ -public final class RegisteredClientOidcClientRegistrationConverter - implements Converter { - - @Override - public OidcClientRegistration convert(RegisteredClient registeredClient) { - // @formatter:off - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .clientId(registeredClient.getClientId()) - .clientIdIssuedAt(registeredClient.getClientIdIssuedAt()) - .clientName(registeredClient.getClientName()); - - if (registeredClient.getClientSecret() != null) { - builder.clientSecret(registeredClient.getClientSecret()); - } - - if (registeredClient.getClientSecretExpiresAt() != null) { - builder.clientSecretExpiresAt(registeredClient.getClientSecretExpiresAt()); - } - - builder.redirectUris((redirectUris) -> - redirectUris.addAll(registeredClient.getRedirectUris())); - - if (!CollectionUtils.isEmpty(registeredClient.getPostLogoutRedirectUris())) { - builder.postLogoutRedirectUris((postLogoutRedirectUris) -> - postLogoutRedirectUris.addAll(registeredClient.getPostLogoutRedirectUris())); - } - - builder.grantTypes((grantTypes) -> - registeredClient.getAuthorizationGrantTypes().forEach((authorizationGrantType) -> - grantTypes.add(authorizationGrantType.getValue()))); - - if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) { - builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue()); - } - - if (!CollectionUtils.isEmpty(registeredClient.getScopes())) { - builder.scopes((scopes) -> - scopes.addAll(registeredClient.getScopes())); - } - - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer()) - .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint()) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .toUriString(); - - builder - .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()) - .idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()) - .registrationClientUrl(registrationClientUri); - - ClientSettings clientSettings = registeredClient.getClientSettings(); - - if (clientSettings.getJwkSetUrl() != null) { - builder.jwkSetUrl(clientSettings.getJwkSetUrl()); - } - - if (clientSettings.getTokenEndpointAuthenticationSigningAlgorithm() != null) { - builder.tokenEndpointAuthenticationSigningAlgorithm(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm().getName()); - } - - return builder.build(); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java deleted file mode 100644 index 12bc8a91b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.http.converter; - -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.GsonHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.util.ClassUtils; - -/** - * Utility methods for {@link HttpMessageConverter}'s. - * - * @author Joe Grandja - * @author l uamas - * @since 0.1.0 - */ -final class HttpMessageConverters { - - private static final boolean jackson2Present; - - private static final boolean gsonPresent; - - private static final boolean jsonbPresent; - - static { - ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); - jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) - && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); - gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); - jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); - } - - private HttpMessageConverters() { - } - - static GenericHttpMessageConverter getJsonMessageConverter() { - if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); - } - if (gsonPresent) { - return new GsonHttpMessageConverter(); - } - if (jsonbPresent) { - return new JsonbHttpMessageConverter(); - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java deleted file mode 100644 index ccb0056a8..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.http.converter; - -import java.net.URL; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * A {@link HttpMessageConverter} for an {@link OidcClientRegistration OpenID Client - * Registration Request and Response}. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.1.1 - * @see AbstractHttpMessageConverter - * @see OidcClientRegistration - */ -public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMessageConverter { - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private Converter, OidcClientRegistration> clientRegistrationConverter = new MapOidcClientRegistrationConverter(); - - private Converter> clientRegistrationParametersConverter = new OidcClientRegistrationMapConverter(); - - public OidcClientRegistrationHttpMessageConverter() { - super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); - } - - @Override - protected boolean supports(Class clazz) { - return OidcClientRegistration.class.isAssignableFrom(clazz); - } - - @Override - @SuppressWarnings("unchecked") - protected OidcClientRegistration readInternal(Class clazz, - HttpInputMessage inputMessage) throws HttpMessageNotReadableException { - try { - Map clientRegistrationParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); - return this.clientRegistrationConverter.convert(clientRegistrationParameters); - } - catch (Exception ex) { - throw new HttpMessageNotReadableException( - "An error occurred reading the OpenID Client Registration: " + ex.getMessage(), ex, inputMessage); - } - } - - @Override - protected void writeInternal(OidcClientRegistration clientRegistration, HttpOutputMessage outputMessage) - throws HttpMessageNotWritableException { - try { - Map clientRegistrationParameters = this.clientRegistrationParametersConverter - .convert(clientRegistration); - this.jsonMessageConverter.write(clientRegistrationParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); - } - catch (Exception ex) { - throw new HttpMessageNotWritableException( - "An error occurred writing the OpenID Client Registration: " + ex.getMessage(), ex); - } - } - - /** - * Sets the {@link Converter} used for converting the OpenID Client Registration - * parameters to an {@link OidcClientRegistration}. - * @param clientRegistrationConverter the {@link Converter} used for converting to an - * {@link OidcClientRegistration} - */ - public final void setClientRegistrationConverter( - Converter, OidcClientRegistration> clientRegistrationConverter) { - Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null"); - this.clientRegistrationConverter = clientRegistrationConverter; - } - - /** - * Sets the {@link Converter} used for converting the {@link OidcClientRegistration} - * to a {@code Map} representation of the OpenID Client Registration parameters. - * @param clientRegistrationParametersConverter the {@link Converter} used for - * converting to a {@code Map} representation of the OpenID Client Registration - * parameters - */ - public final void setClientRegistrationParametersConverter( - Converter> clientRegistrationParametersConverter) { - Assert.notNull(clientRegistrationParametersConverter, "clientRegistrationParametersConverter cannot be null"); - this.clientRegistrationParametersConverter = clientRegistrationParametersConverter; - } - - private static final class MapOidcClientRegistrationConverter - implements Converter, OidcClientRegistration> { - - private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService - .getSharedInstance(); - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); - - private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class); - - private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class); - - private static final Converter INSTANT_CONVERTER = getConverter(INSTANT_TYPE_DESCRIPTOR); - - private final ClaimTypeConverter claimTypeConverter; - - private MapOidcClientRegistrationConverter() { - Converter stringConverter = getConverter(STRING_TYPE_DESCRIPTOR); - Converter collectionStringConverter = getConverter( - TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR)); - Converter urlConverter = getConverter(URL_TYPE_DESCRIPTOR); - - Map> claimConverters = new HashMap<>(); - claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID, stringConverter); - claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, INSTANT_CONVERTER); - claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET, stringConverter); - claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, - MapOidcClientRegistrationConverter::convertClientSecretExpiresAt); - claimConverters.put(OidcClientMetadataClaimNames.CLIENT_NAME, stringConverter); - claimConverters.put(OidcClientMetadataClaimNames.REDIRECT_URIS, collectionStringConverter); - claimConverters.put(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, collectionStringConverter); - claimConverters.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, stringConverter); - claimConverters.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, stringConverter); - claimConverters.put(OidcClientMetadataClaimNames.GRANT_TYPES, collectionStringConverter); - claimConverters.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, collectionStringConverter); - claimConverters.put(OidcClientMetadataClaimNames.SCOPE, MapOidcClientRegistrationConverter::convertScope); - claimConverters.put(OidcClientMetadataClaimNames.JWKS_URI, urlConverter); - claimConverters.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, stringConverter); - this.claimTypeConverter = new ClaimTypeConverter(claimConverters); - } - - @Override - public OidcClientRegistration convert(Map source) { - Map parsedClaims = this.claimTypeConverter.convert(source); - Object clientSecretExpiresAt = parsedClaims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT); - if (clientSecretExpiresAt instanceof Number && clientSecretExpiresAt.equals(0)) { - parsedClaims.remove(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT); - } - return OidcClientRegistration.withClaims(parsedClaims).build(); - } - - private static Converter getConverter(TypeDescriptor targetDescriptor) { - return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); - } - - private static Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) { - if (clientSecretExpiresAt != null && String.valueOf(clientSecretExpiresAt).equals("0")) { - // 0 indicates that client_secret_expires_at does not expire - return null; - } - return (Instant) INSTANT_CONVERTER.convert(clientSecretExpiresAt); - } - - private static List convertScope(Object scope) { - if (scope == null) { - return Collections.emptyList(); - } - return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " ")); - } - - } - - private static final class OidcClientRegistrationMapConverter - implements Converter> { - - @Override - public Map convert(OidcClientRegistration source) { - Map responseClaims = new LinkedHashMap<>(source.getClaims()); - if (source.getClientIdIssuedAt() != null) { - responseClaims.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, - source.getClientIdIssuedAt().getEpochSecond()); - } - if (source.getClientSecret() != null) { - long clientSecretExpiresAt = 0; - if (source.getClientSecretExpiresAt() != null) { - clientSecretExpiresAt = source.getClientSecretExpiresAt().getEpochSecond(); - } - responseClaims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt); - } - if (!CollectionUtils.isEmpty(source.getScopes())) { - responseClaims.put(OidcClientMetadataClaimNames.SCOPE, - StringUtils.collectionToDelimitedString(source.getScopes(), " ")); - } - return responseClaims; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java deleted file mode 100644 index a565802c2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.http.converter; - -import java.net.URL; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderMetadataClaimNames; -import org.springframework.util.Assert; - -/** - * A {@link HttpMessageConverter} for an {@link OidcProviderConfiguration OpenID Provider - * Configuration Response}. - * - * @author Daniel Garnier-Moiroux - * @since 0.1.0 - * @see AbstractHttpMessageConverter - * @see OidcProviderConfiguration - */ -public class OidcProviderConfigurationHttpMessageConverter - extends AbstractHttpMessageConverter { - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private Converter, OidcProviderConfiguration> providerConfigurationConverter = new OidcProviderConfigurationConverter(); - - private Converter> providerConfigurationParametersConverter = OidcProviderConfiguration::getClaims; - - public OidcProviderConfigurationHttpMessageConverter() { - super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); - } - - @Override - protected boolean supports(Class clazz) { - return OidcProviderConfiguration.class.isAssignableFrom(clazz); - } - - @Override - @SuppressWarnings("unchecked") - protected OidcProviderConfiguration readInternal(Class clazz, - HttpInputMessage inputMessage) throws HttpMessageNotReadableException { - try { - Map providerConfigurationParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); - return this.providerConfigurationConverter.convert(providerConfigurationParameters); - } - catch (Exception ex) { - throw new HttpMessageNotReadableException( - "An error occurred reading the OpenID Provider Configuration: " + ex.getMessage(), ex, - inputMessage); - } - } - - @Override - protected void writeInternal(OidcProviderConfiguration providerConfiguration, HttpOutputMessage outputMessage) - throws HttpMessageNotWritableException { - try { - Map providerConfigurationResponseParameters = this.providerConfigurationParametersConverter - .convert(providerConfiguration); - this.jsonMessageConverter.write(providerConfigurationResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); - } - catch (Exception ex) { - throw new HttpMessageNotWritableException( - "An error occurred writing the OpenID Provider Configuration: " + ex.getMessage(), ex); - } - } - - /** - * Sets the {@link Converter} used for converting the OpenID Provider Configuration - * parameters to an {@link OidcProviderConfiguration}. - * @param providerConfigurationConverter the {@link Converter} used for converting to - * an {@link OidcProviderConfiguration} - */ - public final void setProviderConfigurationConverter( - Converter, OidcProviderConfiguration> providerConfigurationConverter) { - Assert.notNull(providerConfigurationConverter, "providerConfigurationConverter cannot be null"); - this.providerConfigurationConverter = providerConfigurationConverter; - } - - /** - * Sets the {@link Converter} used for converting the - * {@link OidcProviderConfiguration} to a {@code Map} representation of the OpenID - * Provider Configuration. - * @param providerConfigurationParametersConverter the {@link Converter} used for - * converting to a {@code Map} representation of the OpenID Provider Configuration - */ - public final void setProviderConfigurationParametersConverter( - Converter> providerConfigurationParametersConverter) { - Assert.notNull(providerConfigurationParametersConverter, - "providerConfigurationParametersConverter cannot be null"); - this.providerConfigurationParametersConverter = providerConfigurationParametersConverter; - } - - private static final class OidcProviderConfigurationConverter - implements Converter, OidcProviderConfiguration> { - - private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService - .getSharedInstance(); - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); - - private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class); - - private final ClaimTypeConverter claimTypeConverter; - - private OidcProviderConfigurationConverter() { - Converter collectionStringConverter = getConverter( - TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR)); - Converter urlConverter = getConverter(URL_TYPE_DESCRIPTOR); - - Map> claimConverters = new HashMap<>(); - claimConverters.put(OidcProviderMetadataClaimNames.ISSUER, urlConverter); - claimConverters.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter); - claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, urlConverter); - claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, - collectionStringConverter); - claimConverters.put(OidcProviderMetadataClaimNames.JWKS_URI, urlConverter); - claimConverters.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, urlConverter); - claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter); - claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter); - claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter); - claimConverters.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, - collectionStringConverter); - claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter); - this.claimTypeConverter = new ClaimTypeConverter(claimConverters); - } - - @Override - public OidcProviderConfiguration convert(Map source) { - Map parsedClaims = this.claimTypeConverter.convert(source); - return OidcProviderConfiguration.withClaims(parsedClaims).build(); - } - - private static Converter getConverter(TypeDescriptor targetDescriptor) { - return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java deleted file mode 100644 index 0b7b38cb3..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.http.converter; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.util.Assert; - -/** - * A {@link HttpMessageConverter} for an {@link OidcUserInfo OpenID Connect UserInfo - * Response}. - * - * @author Ido Salomon - * @author Steve Riesenberg - * @since 0.2.1 - * @see AbstractHttpMessageConverter - * @see OidcUserInfo - */ -public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConverter { - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private Converter, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter(); - - private Converter> userInfoParametersConverter = OidcUserInfo::getClaims; - - public OidcUserInfoHttpMessageConverter() { - super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); - } - - @Override - protected boolean supports(Class clazz) { - return OidcUserInfo.class.isAssignableFrom(clazz); - } - - @Override - @SuppressWarnings("unchecked") - protected OidcUserInfo readInternal(Class clazz, HttpInputMessage inputMessage) - throws HttpMessageNotReadableException { - try { - Map userInfoParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); - return this.userInfoConverter.convert(userInfoParameters); - } - catch (Exception ex) { - throw new HttpMessageNotReadableException( - "An error occurred reading the UserInfo response: " + ex.getMessage(), ex, inputMessage); - } - } - - @Override - protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage outputMessage) - throws HttpMessageNotWritableException { - try { - Map userInfoResponseParameters = this.userInfoParametersConverter.convert(oidcUserInfo); - this.jsonMessageConverter.write(userInfoResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); - } - catch (Exception ex) { - throw new HttpMessageNotWritableException( - "An error occurred writing the UserInfo response: " + ex.getMessage(), ex); - } - } - - /** - * Sets the {@link Converter} used for converting the UserInfo parameters to an - * {@link OidcUserInfo}. - * @param userInfoConverter the {@link Converter} used for converting to an - * {@link OidcUserInfo} - */ - public final void setUserInfoConverter(Converter, OidcUserInfo> userInfoConverter) { - Assert.notNull(userInfoConverter, "userInfoConverter cannot be null"); - this.userInfoConverter = userInfoConverter; - } - - /** - * Sets the {@link Converter} used for converting the {@link OidcUserInfo} to a - * {@code Map} representation of the UserInfo. - * @param userInfoParametersConverter the {@link Converter} used for converting to a - * {@code Map} representation of the UserInfo - */ - public final void setUserInfoParametersConverter( - Converter> userInfoParametersConverter) { - Assert.notNull(userInfoParametersConverter, "userInfoParametersConverter cannot be null"); - this.userInfoParametersConverter = userInfoParametersConverter; - } - - private static final class MapOidcUserInfoConverter implements Converter, OidcUserInfo> { - - private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService - .getSharedInstance(); - - private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class); - - private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class); - - private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); - - private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class); - - private static final TypeDescriptor STRING_OBJECT_MAP_DESCRIPTOR = TypeDescriptor.map(Map.class, - STRING_TYPE_DESCRIPTOR, OBJECT_TYPE_DESCRIPTOR); - - private final ClaimTypeConverter claimTypeConverter; - - private MapOidcUserInfoConverter() { - Converter booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR); - Converter stringConverter = getConverter(STRING_TYPE_DESCRIPTOR); - Converter instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR); - Converter mapConverter = getConverter(STRING_OBJECT_MAP_DESCRIPTOR); - - Map> claimConverters = new HashMap<>(); - claimConverters.put(StandardClaimNames.SUB, stringConverter); - claimConverters.put(StandardClaimNames.NAME, stringConverter); - claimConverters.put(StandardClaimNames.GIVEN_NAME, stringConverter); - claimConverters.put(StandardClaimNames.FAMILY_NAME, stringConverter); - claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter); - claimConverters.put(StandardClaimNames.NICKNAME, stringConverter); - claimConverters.put(StandardClaimNames.PREFERRED_USERNAME, stringConverter); - claimConverters.put(StandardClaimNames.PROFILE, stringConverter); - claimConverters.put(StandardClaimNames.PICTURE, stringConverter); - claimConverters.put(StandardClaimNames.WEBSITE, stringConverter); - claimConverters.put(StandardClaimNames.EMAIL, stringConverter); - claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter); - claimConverters.put(StandardClaimNames.GENDER, stringConverter); - claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter); - claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter); - claimConverters.put(StandardClaimNames.LOCALE, stringConverter); - claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter); - claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter); - claimConverters.put(StandardClaimNames.ADDRESS, mapConverter); - claimConverters.put(StandardClaimNames.UPDATED_AT, instantConverter); - - this.claimTypeConverter = new ClaimTypeConverter(claimConverters); - } - - @Override - public OidcUserInfo convert(Map source) { - Map parsedClaims = this.claimTypeConverter.convert(source); - return new OidcUserInfo(parsedClaims); - } - - private static Converter getConverter(TypeDescriptor targetDescriptor) { - return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java deleted file mode 100644 index 2fce45528..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} that processes OpenID Connect 1.0 Dynamic Client Registration (and - * Client Read) Requests. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @since 0.1.1 - * @see OidcClientRegistration - * @see OidcClientRegistrationAuthenticationConverter - * @see OidcClientRegistrationAuthenticationProvider - * @see OidcClientConfigurationAuthenticationProvider - * @see 3. - * Client Registration Endpoint - * @see 4. - * Client Configuration Endpoint - */ -public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for OpenID Client Registration requests. - */ - private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher clientRegistrationEndpointMatcher; - - private final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - private AuthenticationConverter authenticationConverter = new OidcClientRegistrationAuthenticationConverter(); - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendClientRegistrationResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - - /** - * Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OidcClientRegistrationEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param clientRegistrationEndpointUri the endpoint {@code URI} for OpenID Client - * Registration requests - */ - public OidcClientRegistrationEndpointFilter(AuthenticationManager authenticationManager, - String clientRegistrationEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(clientRegistrationEndpointUri, "clientRegistrationEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.clientRegistrationEndpointMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, clientRegistrationEndpointUri), - createClientConfigurationMatcher(clientRegistrationEndpointUri)); - } - - private static RequestMatcher createClientConfigurationMatcher(String clientRegistrationEndpointUri) { - RequestMatcher clientConfigurationGetMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, clientRegistrationEndpointUri); - - RequestMatcher clientIdMatcher = (request) -> { - String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID); - return StringUtils.hasText(clientId); - }; - - return new AndRequestMatcher(clientConfigurationGetMatcher, clientIdMatcher); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.clientRegistrationEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication clientRegistrationAuthentication = this.authenticationConverter.convert(request); - - Authentication clientRegistrationAuthenticationResult = this.authenticationManager - .authenticate(clientRegistrationAuthentication); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - clientRegistrationAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Client registration request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Connect 1.0 Client Registration Error: " + ex.getMessage(), - "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError"); - if (this.logger.isTraceEnabled()) { - this.logger.trace(error.getDescription(), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, - new OAuth2AuthenticationException(error)); - } - finally { - SecurityContextHolder.clearContext(); - } - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Client - * Registration Request from {@link HttpServletRequest} to an instance of - * {@link OidcClientRegistrationAuthenticationToken} used for authenticating the - * request. - * @param authenticationConverter an {@link AuthenticationConverter} used when - * attempting to extract a Client Registration Request from {@link HttpServletRequest} - * @since 0.4.0 - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcClientRegistrationAuthenticationToken} and returning the - * {@link OidcClientRegistration Client Registration Response}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OidcClientRegistrationAuthenticationToken} - * @since 0.4.0 - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - * @since 0.4.0 - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void sendClientRegistrationResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - OidcClientRegistration clientRegistration = ((OidcClientRegistrationAuthenticationToken) authentication) - .getClientRegistration(); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - if (HttpMethod.POST.name().equals(request.getMethod())) { - httpResponse.setStatusCode(HttpStatus.CREATED); - } - else { - httpResponse.setStatusCode(HttpStatus.OK); - } - this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpResponse); - } - - private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authenticationException) throws IOException { - OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError(); - HttpStatus httpStatus = HttpStatus.BAD_REQUEST; - if (OAuth2ErrorCodes.INVALID_TOKEN.equals(error.getErrorCode())) { - httpStatus = HttpStatus.UNAUTHORIZED; - } - else if (OAuth2ErrorCodes.INSUFFICIENT_SCOPE.equals(error.getErrorCode())) { - httpStatus = HttpStatus.FORBIDDEN; - } - else if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) { - httpStatus = HttpStatus.UNAUTHORIZED; - } - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(httpStatus); - this.errorHttpResponseConverter.write(error, null, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java deleted file mode 100644 index e92629251..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcLogoutAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcLogoutAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} that processes OpenID Connect 1.0 RP-Initiated Logout Requests. - * - * @author Joe Grandja - * @since 1.1 - * @see OidcLogoutAuthenticationConverter - * @see OidcLogoutAuthenticationSuccessHandler - * @see OidcLogoutAuthenticationProvider - * @see 2. - * RP-Initiated Logout - */ -public final class OidcLogoutEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for OpenID Connect 1.0 RP-Initiated Logout - * Requests. - */ - private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher logoutEndpointMatcher; - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = new OidcLogoutAuthenticationSuccessHandler(); - - private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - - /** - * Constructs an {@code OidcLogoutEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - */ - public OidcLogoutEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_OIDC_LOGOUT_ENDPOINT_URI); - } - - /** - * Constructs an {@code OidcLogoutEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - * @param logoutEndpointUri the endpoint {@code URI} for OpenID Connect 1.0 - * RP-Initiated Logout Requests - */ - public OidcLogoutEndpointFilter(AuthenticationManager authenticationManager, String logoutEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(logoutEndpointUri, "logoutEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.logoutEndpointMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, logoutEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, logoutEndpointUri)); - this.authenticationConverter = new OidcLogoutAuthenticationConverter(); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.logoutEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication oidcLogoutAuthentication = this.authenticationConverter.convert(request); - - Authentication oidcLogoutAuthenticationResult = this.authenticationManager - .authenticate(oidcLogoutAuthentication); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - oidcLogoutAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Logout request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Connect 1.0 RP-Initiated Logout Error: " + ex.getMessage(), - "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - if (this.logger.isTraceEnabled()) { - this.logger.trace(error, ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, - new OAuth2AuthenticationException(error)); - } - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Logout - * Request from {@link HttpServletRequest} to an instance of - * {@link OidcLogoutAuthenticationToken} used for authenticating the request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract a Logout Request from {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcLogoutAuthenticationToken} and performing the logout. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OidcLogoutAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException { - - OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); - response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString()); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java deleted file mode 100644 index 3f1befe12..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web; - -import java.io.IOException; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Filter} that processes OpenID Provider Configuration Requests. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.0 - * @see OidcProviderConfiguration - * @see AuthorizationServerSettings - * @see 4.1. - * OpenID Provider Configuration Request - */ -public final class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for OpenID Provider Configuration requests. - */ - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - - private final RequestMatcher requestMatcher = createRequestMatcher(); - - private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter = new OidcProviderConfigurationHttpMessageConverter(); - - private Consumer providerConfigurationCustomizer = (providerConfiguration) -> { - }; - - /** - * Sets the {@code Consumer} providing access to the - * {@link OidcProviderConfiguration.Builder} allowing the ability to customize the - * claims of the OpenID Provider's configuration. - * @param providerConfigurationCustomizer the {@code Consumer} providing access to the - * {@link OidcProviderConfiguration.Builder} - * @since 0.4.0 - */ - public void setProviderConfigurationCustomizer( - Consumer providerConfigurationCustomizer) { - Assert.notNull(providerConfigurationCustomizer, "providerConfigurationCustomizer cannot be null"); - this.providerConfigurationCustomizer = providerConfigurationCustomizer; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.requestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String issuer = authorizationServerContext.getIssuer(); - AuthorizationServerSettings authorizationServerSettings = authorizationServerContext - .getAuthorizationServerSettings(); - - OidcProviderConfiguration.Builder providerConfiguration = OidcProviderConfiguration.builder() - .issuer(issuer) - .authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint())) - .pushedAuthorizationRequestEndpoint( - asUrl(issuer, authorizationServerSettings.getPushedAuthorizationRequestEndpoint())) - .deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint())) - .tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint())) - .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) - .jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint())) - .userInfoEndpoint(asUrl(issuer, authorizationServerSettings.getOidcUserInfoEndpoint())) - .endSessionEndpoint(asUrl(issuer, authorizationServerSettings.getOidcLogoutEndpoint())) - .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue()) - .grantType(AuthorizationGrantType.DEVICE_CODE.getValue()) - .grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()) - .tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint())) - .tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods()) - .tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint())) - .tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods()) - .codeChallengeMethod("S256") - .tlsClientCertificateBoundAccessTokens(true) - .dPoPSigningAlgorithms(dPoPSigningAlgorithms()) - .subjectType("public") - .idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName()) - .scope(OidcScopes.OPENID); - - this.providerConfigurationCustomizer.accept(providerConfiguration); - - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.providerConfigurationHttpMessageConverter.write(providerConfiguration.build(), MediaType.APPLICATION_JSON, - httpResponse); - } - - private static RequestMatcher createRequestMatcher() { - final RequestMatcher defaultRequestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI); - final RequestMatcher multipleIssuersRequestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, "/**" + DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI); - return (request) -> AuthorizationServerContextHolder.getContext() - .getAuthorizationServerSettings() - .isMultipleIssuersAllowed() ? multipleIssuersRequestMatcher.matches(request) - : defaultRequestMatcher.matches(request); - } - - private static Consumer> clientAuthenticationMethods() { - return (authenticationMethods) -> { - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.TLS_CLIENT_AUTH.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.getValue()); - }; - } - - private static Consumer> dPoPSigningAlgorithms() { - return (algs) -> { - algs.add(JwsAlgorithms.RS256); - algs.add(JwsAlgorithms.RS384); - algs.add(JwsAlgorithms.RS512); - algs.add(JwsAlgorithms.PS256); - algs.add(JwsAlgorithms.PS384); - algs.add(JwsAlgorithms.PS512); - algs.add(JwsAlgorithms.ES256); - algs.add(JwsAlgorithms.ES384); - algs.add(JwsAlgorithms.ES512); - }; - } - - private static String asUrl(String issuer, String endpoint) { - return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java deleted file mode 100644 index 40fdf3fa2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcUserInfoHttpMessageConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} that processes OpenID Connect 1.0 UserInfo Requests. - * - * @author Ido Salomon - * @author Steve Riesenberg - * @author Daniel Garnier-Moiroux - * @since 0.2.1 - * @see OidcUserInfo - * @see OidcUserInfoAuthenticationProvider - * @see 5.3. - * UserInfo Endpoint - */ -public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for OpenID Connect 1.0 UserInfo Requests. - */ - private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher userInfoEndpointMatcher; - - private final HttpMessageConverter userInfoHttpMessageConverter = new OidcUserInfoHttpMessageConverter(); - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - private AuthenticationConverter authenticationConverter = this::createAuthentication; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendUserInfoResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - - /** - * Constructs an {@code OidcUserInfoEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - */ - public OidcUserInfoEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_OIDC_USER_INFO_ENDPOINT_URI); - } - - /** - * Constructs an {@code OidcUserInfoEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - * @param userInfoEndpointUri the endpoint {@code URI} for OpenID Connect 1.0 UserInfo - * Requests - */ - public OidcUserInfoEndpointFilter(AuthenticationManager authenticationManager, String userInfoEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(userInfoEndpointUri, "userInfoEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.userInfoEndpointMatcher = new OrRequestMatcher( - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, userInfoEndpointUri), - PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, userInfoEndpointUri)); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.userInfoEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication userInfoAuthentication = this.authenticationConverter.convert(request); - - Authentication userInfoAuthenticationResult = this.authenticationManager - .authenticate(userInfoAuthentication); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, userInfoAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("User info request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Connect 1.0 UserInfo Error: " + ex.getMessage(), - "https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError"); - if (this.logger.isTraceEnabled()) { - this.logger.trace(error.getDescription(), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, - new OAuth2AuthenticationException(error)); - } - finally { - SecurityContextHolder.clearContext(); - } - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract an - * UserInfo Request from {@link HttpServletRequest} to an instance of - * {@link OidcUserInfoAuthenticationToken} used for authenticating the request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract an UserInfo Request from {@link HttpServletRequest} - * @since 0.4.0 - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcUserInfoAuthenticationToken} and returning the {@link OidcUserInfo - * UserInfo Response}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OidcUserInfoAuthenticationToken} - * @since 0.4.0 - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - * @since 0.4.0 - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private Authentication createAuthentication(HttpServletRequest request) { - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - return new OidcUserInfoAuthenticationToken(principal); - } - - private void sendUserInfoResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - OidcUserInfoAuthenticationToken userInfoAuthenticationToken = (OidcUserInfoAuthenticationToken) authentication; - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.userInfoHttpMessageConverter.write(userInfoAuthenticationToken.getUserInfo(), null, httpResponse); - } - - private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authenticationException) throws IOException { - OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError(); - HttpStatus httpStatus = HttpStatus.BAD_REQUEST; - if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_TOKEN)) { - httpStatus = HttpStatus.UNAUTHORIZED; - } - else if (error.getErrorCode().equals(OAuth2ErrorCodes.INSUFFICIENT_SCOPE)) { - httpStatus = HttpStatus.FORBIDDEN; - } - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(httpStatus); - this.errorHttpResponseConverter.write(error, null, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OAuth2EndpointUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OAuth2EndpointUtils.java deleted file mode 100644 index 0eb6d711a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OAuth2EndpointUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.web.authentication; - -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Utility methods for the OAuth 2.0 Protocol Endpoints. - * - * @author Joe Grandja - * @author Greg Li - * @since 1.1.4 - */ -final class OAuth2EndpointUtils { - - private OAuth2EndpointUtils() { - } - - static MultiValueMap getFormParameters(HttpServletRequest request) { - Map parameterMap = request.getParameterMap(); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameterMap.forEach((key, values) -> { - String queryString = StringUtils.hasText(request.getQueryString()) ? request.getQueryString() : ""; - // If not query parameter then it's a form parameter - if (!queryString.contains(key) && values.length > 0) { - for (String value : values) { - parameters.add(key, value); - } - } - }); - return parameters; - } - - static MultiValueMap getQueryParameters(HttpServletRequest request) { - Map parameterMap = request.getParameterMap(); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameterMap.forEach((key, values) -> { - String queryString = StringUtils.hasText(request.getQueryString()) ? request.getQueryString() : ""; - if (queryString.contains(key) && values.length > 0) { - for (String value : values) { - parameters.add(key, value); - } - } - }); - return parameters; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java deleted file mode 100644 index e383138f7..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.web.authentication; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an OpenID Connect 1.0 Dynamic Client Registration (or Client Read) - * Request from {@link HttpServletRequest} and then converts to an - * {@link OidcClientRegistrationAuthenticationToken} used for authenticating the request. - * - * @author Joe Grandja - * @since 0.4.0 - * @see AuthenticationConverter - * @see OidcClientRegistrationAuthenticationToken - * @see OidcClientRegistrationEndpointFilter - */ -public final class OidcClientRegistrationAuthenticationConverter implements AuthenticationConverter { - - private final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); - - @Override - public Authentication convert(HttpServletRequest request) { - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - - if ("POST".equals(request.getMethod())) { - OidcClientRegistration clientRegistration; - try { - clientRegistration = this.clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, - new ServletServerHttpRequest(request)); - } - catch (Exception ex) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Client Registration Error: " + ex.getMessage(), - "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError"); - throw new OAuth2AuthenticationException(error, ex); - } - return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); - } - - MultiValueMap parameters = OAuth2EndpointUtils.getQueryParameters(request); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - return new OidcClientRegistrationAuthenticationToken(principal, clientId); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java deleted file mode 100644 index 5ed7d8362..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.web.authentication; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an OpenID Connect 1.0 RP-Initiated Logout Request from - * {@link HttpServletRequest} and then converts to an - * {@link OidcLogoutAuthenticationToken} used for authenticating the request. - * - * @author Joe Grandja - * @since 1.1 - * @see AuthenticationConverter - * @see OidcLogoutAuthenticationToken - * @see OidcLogoutEndpointFilter - */ -public final class OidcLogoutAuthenticationConverter implements AuthenticationConverter { - - private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", - "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = "GET".equals(request.getMethod()) - ? OAuth2EndpointUtils.getQueryParameters(request) : OAuth2EndpointUtils.getFormParameters(request); - - // id_token_hint (REQUIRED) // RECOMMENDED as per spec - String idTokenHint = parameters.getFirst("id_token_hint"); - if (!StringUtils.hasText(idTokenHint) || parameters.get("id_token_hint").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "id_token_hint"); - } - - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { - principal = ANONYMOUS_AUTHENTICATION; - } - - String sessionId = null; - HttpSession session = request.getSession(false); - if (session != null) { - sessionId = session.getId(); - } - - // client_id (OPTIONAL) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (StringUtils.hasText(clientId) && parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); - } - - // post_logout_redirect_uri (OPTIONAL) - String postLogoutRedirectUri = parameters.getFirst("post_logout_redirect_uri"); - if (StringUtils.hasText(postLogoutRedirectUri) && parameters.get("post_logout_redirect_uri").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri"); - } - - // state (OPTIONAL) - String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); - } - - return new OidcLogoutAuthenticationToken(idTokenHint, principal, sessionId, clientId, postLogoutRedirectUri, - state); - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OpenID Connect 1.0 Logout Request Parameter: " + parameterName, - "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java deleted file mode 100644 index 150088234..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web.authentication; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * An implementation of an {@link AuthenticationSuccessHandler} used for handling an - * {@link OidcLogoutAuthenticationToken} and performing the OpenID Connect 1.0 - * RP-Initiated Logout. - * - * @author Joe Grandja - * @since 1.4 - * @see OidcLogoutEndpointFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler) - * @see LogoutHandler - */ -public final class OidcLogoutAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - - private final Log logger = LogFactory.getLog(getClass()); - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - private final SecurityContextLogoutHandler securityContextLogoutHandler = new SecurityContextLogoutHandler(); - - private LogoutHandler logoutHandler = this::performLogout; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - - if (!(authentication instanceof OidcLogoutAuthenticationToken)) { - if (this.logger.isErrorEnabled()) { - this.logger.error(Authentication.class.getSimpleName() + " must be of type " - + OidcLogoutAuthenticationToken.class.getName() + " but was " - + authentication.getClass().getName()); - } - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Unable to process the OpenID Connect 1.0 RP-Initiated Logout response.", null); - throw new OAuth2AuthenticationException(error); - } - - this.logoutHandler.logout(request, response, authentication); - - sendLogoutRedirect(request, response, authentication); - } - - /** - * Sets the {@link LogoutHandler} used for performing logout. - * @param logoutHandler the {@link LogoutHandler} used for performing logout - */ - public void setLogoutHandler(LogoutHandler logoutHandler) { - Assert.notNull(logoutHandler, "logoutHandler cannot be null"); - this.logoutHandler = logoutHandler; - } - - private void performLogout(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication; - - // Check for active user session - if (oidcLogoutAuthentication.isPrincipalAuthenticated()) { - this.securityContextLogoutHandler.logout(request, response, - (Authentication) oidcLogoutAuthentication.getPrincipal()); - } - } - - private void sendLogoutRedirect(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication; - - String redirectUri = "/"; - if (oidcLogoutAuthentication.isAuthenticated() - && StringUtils.hasText(oidcLogoutAuthentication.getPostLogoutRedirectUri())) { - // Use the `post_logout_redirect_uri` parameter - UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(oidcLogoutAuthentication.getPostLogoutRedirectUri()); - if (StringUtils.hasText(oidcLogoutAuthentication.getState())) { - uriBuilder.queryParam(OAuth2ParameterNames.STATE, - UriUtils.encode(oidcLogoutAuthentication.getState(), StandardCharsets.UTF_8)); - } - // build(true) -> Components are explicitly encoded - redirectUri = uriBuilder.build(true).toUriString(); - } - this.redirectStrategy.sendRedirect(request, response, redirectUri); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java deleted file mode 100644 index 43e8f5548..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - -import org.springframework.util.Assert; - -/** - * Base implementation for configuration settings. - * - * @author Joe Grandja - * @since 0.0.2 - */ -public abstract class AbstractSettings implements Serializable { - - @Serial - private static final long serialVersionUID = 7434920549178503670L; - - private final Map settings; - - protected AbstractSettings(Map settings) { - Assert.notEmpty(settings, "settings cannot be empty"); - this.settings = Collections.unmodifiableMap(new HashMap<>(settings)); - } - - /** - * Returns a configuration setting. - * @param name the name of the setting - * @param the type of the setting - * @return the value of the setting, or {@code null} if not available - */ - @SuppressWarnings("unchecked") - public T getSetting(String name) { - Assert.hasText(name, "name cannot be empty"); - return (T) getSettings().get(name); - } - - /** - * Returns a {@code Map} of the configuration settings. - * @return a {@code Map} of the configuration settings - */ - public Map getSettings() { - return this.settings; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - AbstractSettings that = (AbstractSettings) obj; - return this.settings.equals(that.settings); - } - - @Override - public int hashCode() { - return Objects.hash(this.settings); - } - - @Override - public String toString() { - return "AbstractSettings {" + "settings=" + this.settings + '}'; - } - - /** - * A builder for subclasses of {@link AbstractSettings}. - * - * @param the type of object - * @param the type of the builder - */ - protected abstract static class AbstractBuilder> { - - private final Map settings = new HashMap<>(); - - protected AbstractBuilder() { - } - - /** - * Sets a configuration setting. - * @param name the name of the setting - * @param value the value of the setting - * @return the {@link AbstractBuilder} for further configuration - */ - public B setting(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - getSettings().put(name, value); - return getThis(); - } - - /** - * A {@code Consumer} of the configuration settings {@code Map} allowing the - * ability to add, replace, or remove. - * @param settingsConsumer a {@link Consumer} of the configuration settings - * {@code Map} - * @return the {@link AbstractBuilder} for further configuration - */ - public B settings(Consumer> settingsConsumer) { - settingsConsumer.accept(getSettings()); - return getThis(); - } - - public abstract T build(); - - protected final Map getSettings() { - return this.settings; - } - - @SuppressWarnings("unchecked") - protected final B getThis() { - return (B) this; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java deleted file mode 100644 index eb8afbd17..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.io.Serial; -import java.util.Map; - -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.util.Assert; - -/** - * A facility for authorization server configuration settings. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.0 - * @see AbstractSettings - * @see ConfigurationSettingNames.AuthorizationServer - */ -public final class AuthorizationServerSettings extends AbstractSettings { - - @Serial - private static final long serialVersionUID = 2719834789442554660L; - - private AuthorizationServerSettings(Map settings) { - super(settings); - } - - /** - * Returns the URL of the Authorization Server's Issuer Identifier. - * @return the URL of the Authorization Server's Issuer Identifier - */ - public String getIssuer() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.ISSUER); - } - - /** - * Returns {@code true} if multiple issuers are allowed per host. The default is - * {@code false}. Using path components in the URL of the issuer identifier enables - * supporting multiple issuers per host in a multi-tenant hosting configuration. - * - *

- * For example: - *

    - *
  • {@code https://example.com/issuer1}
  • - *
  • {@code https://example.com/authz/issuer2}
  • - *
- * @return {@code true} if multiple issuers are allowed per host, {@code false} - * otherwise - * @since 1.3 - * @see AuthorizationServerContext#getIssuer() - */ - public boolean isMultipleIssuersAllowed() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED); - } - - /** - * Returns the OAuth 2.0 Authorization endpoint. The default is - * {@code /oauth2/authorize}. - * @return the Authorization endpoint - */ - public String getAuthorizationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Pushed Authorization Request endpoint. The default is - * {@code /oauth2/par}. - * @return the Pushed Authorization Request endpoint - * @since 1.5 - */ - public String getPushedAuthorizationRequestEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Device Authorization endpoint. The default is - * {@code /oauth2/device_authorization}. - * @return the Device Authorization endpoint - * @since 1.1 - */ - public String getDeviceAuthorizationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Device Verification endpoint. The default is - * {@code /oauth2/device_verification}. - * @return the Device Verification endpoint - * @since 1.1 - */ - public String getDeviceVerificationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}. - * @return the Token endpoint - */ - public String getTokenEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT); - } - - /** - * Returns the JWK Set endpoint. The default is {@code /oauth2/jwks}. - * @return the JWK Set endpoint - */ - public String getJwkSetEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Token Revocation endpoint. The default is - * {@code /oauth2/revoke}. - * @return the Token Revocation endpoint - */ - public String getTokenRevocationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT); - } - - /** - * Returns the OAuth 2.0 Token Introspection endpoint. The default is - * {@code /oauth2/introspect}. - * @return the Token Introspection endpoint - */ - public String getTokenIntrospectionEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT); - } - - /** - * Returns the OpenID Connect 1.0 Client Registration endpoint. The default is - * {@code /connect/register}. - * @return the OpenID Connect 1.0 Client Registration endpoint - */ - public String getOidcClientRegistrationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT); - } - - /** - * Returns the OpenID Connect 1.0 UserInfo endpoint. The default is {@code /userinfo}. - * @return the OpenID Connect 1.0 UserInfo endpoint - */ - public String getOidcUserInfoEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT); - } - - /** - * Returns the OpenID Connect 1.0 Logout endpoint. The default is - * {@code /connect/logout}. - * @return the OpenID Connect 1.0 Logout endpoint - * @since 1.1 - */ - public String getOidcLogoutEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT); - } - - /** - * Constructs a new {@link Builder} with the default settings. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder().multipleIssuersAllowed(false) - .authorizationEndpoint("/oauth2/authorize") - .pushedAuthorizationRequestEndpoint("/oauth2/par") - .deviceAuthorizationEndpoint("/oauth2/device_authorization") - .deviceVerificationEndpoint("/oauth2/device_verification") - .tokenEndpoint("/oauth2/token") - .jwkSetEndpoint("/oauth2/jwks") - .tokenRevocationEndpoint("/oauth2/revoke") - .tokenIntrospectionEndpoint("/oauth2/introspect") - .oidcClientRegistrationEndpoint("/connect/register") - .oidcUserInfoEndpoint("/userinfo") - .oidcLogoutEndpoint("/connect/logout"); - } - - /** - * Constructs a new {@link Builder} with the provided settings. - * @param settings the settings to initialize the builder - * @return the {@link Builder} - */ - public static Builder withSettings(Map settings) { - Assert.notEmpty(settings, "settings cannot be empty"); - return new Builder().settings((s) -> s.putAll(settings)); - } - - /** - * A builder for {@link AuthorizationServerSettings}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Sets the URL the Authorization Server uses as its Issuer Identifier. - * @param issuer the URL the Authorization Server uses as its Issuer Identifier. - * @return the {@link Builder} for further configuration - */ - public Builder issuer(String issuer) { - return setting(ConfigurationSettingNames.AuthorizationServer.ISSUER, issuer); - } - - /** - * Set to {@code true} if multiple issuers are allowed per host. Using path - * components in the URL of the issuer identifier enables supporting multiple - * issuers per host in a multi-tenant hosting configuration. - * - *

- * For example: - *

    - *
  • {@code https://example.com/issuer1}
  • - *
  • {@code https://example.com/authz/issuer2}
  • - *
- * - *

- * NOTE: Explicitly configuring the issuer identifier via - * {@link #issuer(String)} forces to a single-tenant configuration. Avoid - * configuring the issuer identifier when using a multi-tenant hosting - * configuration, allowing the issuer identifier to be resolved from the - * "current" request. - * @param multipleIssuersAllowed {@code true} if multiple issuers are allowed per - * host, {@code false} otherwise - * @return the {@link Builder} for further configuration - * @since 1.3 - * @see AuthorizationServerContext#getIssuer() - */ - public Builder multipleIssuersAllowed(boolean multipleIssuersAllowed) { - return setting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED, - multipleIssuersAllowed); - } - - /** - * Sets the OAuth 2.0 Authorization endpoint. - * @param authorizationEndpoint the Authorization endpoint - * @return the {@link Builder} for further configuration - */ - public Builder authorizationEndpoint(String authorizationEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT, authorizationEndpoint); - } - - /** - * Sets the OAuth 2.0 Pushed Authorization Request endpoint. - * @param pushedAuthorizationRequestEndpoint the Pushed Authorization Request - * endpoint - * @return the {@link Builder} for further configuration - * @since 1.5 - */ - public Builder pushedAuthorizationRequestEndpoint(String pushedAuthorizationRequestEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, - pushedAuthorizationRequestEndpoint); - } - - /** - * Sets the OAuth 2.0 Device Authorization endpoint. - * @param deviceAuthorizationEndpoint the Device Authorization endpoint - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder deviceAuthorizationEndpoint(String deviceAuthorizationEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT, - deviceAuthorizationEndpoint); - } - - /** - * Sets the OAuth 2.0 Device Verification endpoint. - * @param deviceVerificationEndpoint the Device Verification endpoint - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder deviceVerificationEndpoint(String deviceVerificationEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT, - deviceVerificationEndpoint); - } - - /** - * Sets the OAuth 2.0 Token endpoint. - * @param tokenEndpoint the Token endpoint - * @return the {@link Builder} for further configuration - */ - public Builder tokenEndpoint(String tokenEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT, tokenEndpoint); - } - - /** - * Sets the JWK Set endpoint. - * @param jwkSetEndpoint the JWK Set endpoint - * @return the {@link Builder} for further configuration - */ - public Builder jwkSetEndpoint(String jwkSetEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT, jwkSetEndpoint); - } - - /** - * Sets the OAuth 2.0 Token Revocation endpoint. - * @param tokenRevocationEndpoint the Token Revocation endpoint - * @return the {@link Builder} for further configuration - */ - public Builder tokenRevocationEndpoint(String tokenRevocationEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT, - tokenRevocationEndpoint); - } - - /** - * Sets the OAuth 2.0 Token Introspection endpoint. - * @param tokenIntrospectionEndpoint the Token Introspection endpoint - * @return the {@link Builder} for further configuration - */ - public Builder tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT, - tokenIntrospectionEndpoint); - } - - /** - * Sets the OpenID Connect 1.0 Client Registration endpoint. - * @param oidcClientRegistrationEndpoint the OpenID Connect 1.0 Client - * Registration endpoint - * @return the {@link Builder} for further configuration - */ - public Builder oidcClientRegistrationEndpoint(String oidcClientRegistrationEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT, - oidcClientRegistrationEndpoint); - } - - /** - * Sets the OpenID Connect 1.0 UserInfo endpoint. - * @param oidcUserInfoEndpoint the OpenID Connect 1.0 UserInfo endpoint - * @return the {@link Builder} for further configuration - */ - public Builder oidcUserInfoEndpoint(String oidcUserInfoEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT, oidcUserInfoEndpoint); - } - - /** - * Sets the OpenID Connect 1.0 Logout endpoint. - * @param oidcLogoutEndpoint the OpenID Connect 1.0 Logout endpoint - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder oidcLogoutEndpoint(String oidcLogoutEndpoint) { - return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT, oidcLogoutEndpoint); - } - - /** - * Builds the {@link AuthorizationServerSettings}. - * @return the {@link AuthorizationServerSettings} - */ - @Override - public AuthorizationServerSettings build() { - AuthorizationServerSettings authorizationServerSettings = new AuthorizationServerSettings(getSettings()); - if (authorizationServerSettings.getIssuer() != null - && authorizationServerSettings.isMultipleIssuersAllowed()) { - throw new IllegalArgumentException("The issuer identifier (" + authorizationServerSettings.getIssuer() - + ") cannot be set when isMultipleIssuersAllowed() is true."); - } - return authorizationServerSettings; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java deleted file mode 100644 index 46333c9e1..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.io.Serial; -import java.util.Map; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.util.Assert; - -/** - * A facility for client configuration settings. - * - * @author Joe Grandja - * @since 0.0.2 - * @see AbstractSettings - * @see ConfigurationSettingNames.Client - */ -public final class ClientSettings extends AbstractSettings { - - @Serial - private static final long serialVersionUID = 9015034829752473931L; - - private ClientSettings(Map settings) { - super(settings); - } - - /** - * Returns {@code true} if the client is required to provide a proof key challenge and - * verifier when performing the Authorization Code Grant flow. The default is - * {@code false}. - * @return {@code true} if the client is required to provide a proof key challenge and - * verifier, {@code false} otherwise - */ - public boolean isRequireProofKey() { - return getSetting(ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY); - } - - /** - * Returns {@code true} if authorization consent is required when the client requests - * access. The default is {@code false}. - * @return {@code true} if authorization consent is required when the client requests - * access, {@code false} otherwise - */ - public boolean isRequireAuthorizationConsent() { - return getSetting(ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT); - } - - /** - * Returns the {@code URL} for the Client's JSON Web Key Set. - * @return the {@code URL} for the Client's JSON Web Key Set - * @since 0.2.2 - */ - public String getJwkSetUrl() { - return getSetting(ConfigurationSettingNames.Client.JWK_SET_URL); - } - - /** - * Returns the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint for the - * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and - * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} - * authentication methods. - * @return the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint - * @since 0.2.2 - */ - public JwsAlgorithm getTokenEndpointAuthenticationSigningAlgorithm() { - return getSetting(ConfigurationSettingNames.Client.TOKEN_ENDPOINT_AUTHENTICATION_SIGNING_ALGORITHM); - } - - /** - * Returns the expected subject distinguished name associated to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} method. - * @return the expected subject distinguished name associated to the client - * {@code X509Certificate} received during client authentication - * @since 1.3 - */ - public String getX509CertificateSubjectDN() { - return getSetting(ConfigurationSettingNames.Client.X509_CERTIFICATE_SUBJECT_DN); - } - - /** - * Constructs a new {@link Builder} with the default settings. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder().requireProofKey(false).requireAuthorizationConsent(false); - } - - /** - * Constructs a new {@link Builder} with the provided settings. - * @param settings the settings to initialize the builder - * @return the {@link Builder} - */ - public static Builder withSettings(Map settings) { - Assert.notEmpty(settings, "settings cannot be empty"); - return new Builder().settings((s) -> s.putAll(settings)); - } - - /** - * A builder for {@link ClientSettings}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Set to {@code true} if the client is required to provide a proof key challenge - * and verifier when performing the Authorization Code Grant flow. - * @param requireProofKey {@code true} if the client is required to provide a - * proof key challenge and verifier, {@code false} otherwise - * @return the {@link Builder} for further configuration - */ - public Builder requireProofKey(boolean requireProofKey) { - return setting(ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY, requireProofKey); - } - - /** - * Set to {@code true} if authorization consent is required when the client - * requests access. This applies to all interactive flows (e.g. - * {@code authorization_code} and {@code device_code}). - * @param requireAuthorizationConsent {@code true} if authorization consent is - * required when the client requests access, {@code false} otherwise - * @return the {@link Builder} for further configuration - */ - public Builder requireAuthorizationConsent(boolean requireAuthorizationConsent) { - return setting(ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT, requireAuthorizationConsent); - } - - /** - * Sets the {@code URL} for the Client's JSON Web Key Set. - * @param jwkSetUrl the {@code URL} for the Client's JSON Web Key Set - * @return the {@link Builder} for further configuration - * @since 0.2.2 - */ - public Builder jwkSetUrl(String jwkSetUrl) { - return setting(ConfigurationSettingNames.Client.JWK_SET_URL, jwkSetUrl); - } - - /** - * Sets the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint for the - * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and - * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} - * authentication methods. - * @param authenticationSigningAlgorithm the {@link JwsAlgorithm JWS} algorithm - * that must be used for signing the {@link Jwt JWT} used to authenticate the - * Client at the Token Endpoint - * @return the {@link Builder} for further configuration - * @since 0.2.2 - */ - public Builder tokenEndpointAuthenticationSigningAlgorithm(JwsAlgorithm authenticationSigningAlgorithm) { - return setting(ConfigurationSettingNames.Client.TOKEN_ENDPOINT_AUTHENTICATION_SIGNING_ALGORITHM, - authenticationSigningAlgorithm); - } - - /** - * Sets the expected subject distinguished name associated to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} method. - * @param x509CertificateSubjectDN the expected subject distinguished name - * associated to the client {@code X509Certificate} received during client - * authentication * @return the {@link Builder} for further configuration - * @return the {@link Builder} for further configuration - * @since 1.3 - */ - public Builder x509CertificateSubjectDN(String x509CertificateSubjectDN) { - return setting(ConfigurationSettingNames.Client.X509_CERTIFICATE_SUBJECT_DN, x509CertificateSubjectDN); - } - - /** - * Builds the {@link ClientSettings}. - * @return the {@link ClientSettings} - */ - @Override - public ClientSettings build() { - return new ClientSettings(getSettings()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java deleted file mode 100644 index 96edd2a54..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.Jwt; - -/** - * The names for all the configuration settings. - * - * @author Joe Grandja - * @since 0.2.0 - */ -public final class ConfigurationSettingNames { - - private static final String SETTINGS_NAMESPACE = "settings."; - - private ConfigurationSettingNames() { - } - - /** - * The names for client configuration settings. - */ - public static final class Client { - - private static final String CLIENT_SETTINGS_NAMESPACE = SETTINGS_NAMESPACE.concat("client."); - - /** - * Set to {@code true} if the client is required to provide a proof key challenge - * and verifier when performing the Authorization Code Grant flow. - */ - public static final String REQUIRE_PROOF_KEY = CLIENT_SETTINGS_NAMESPACE.concat("require-proof-key"); - - /** - * Set to {@code true} if authorization consent is required when the client - * requests access. This applies to all interactive flows (e.g. - * {@code authorization_code} and {@code device_code}). - */ - public static final String REQUIRE_AUTHORIZATION_CONSENT = CLIENT_SETTINGS_NAMESPACE - .concat("require-authorization-consent"); - - /** - * Set the {@code URL} for the Client's JSON Web Key Set. - * @since 0.2.2 - */ - public static final String JWK_SET_URL = CLIENT_SETTINGS_NAMESPACE.concat("jwk-set-url"); - - /** - * Set the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint for the - * {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and - * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} - * authentication methods. - * @since 0.2.2 - */ - public static final String TOKEN_ENDPOINT_AUTHENTICATION_SIGNING_ALGORITHM = CLIENT_SETTINGS_NAMESPACE - .concat("token-endpoint-authentication-signing-algorithm"); - - /** - * Set the expected subject distinguished name associated to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} method. - * @since 1.3 - */ - public static final String X509_CERTIFICATE_SUBJECT_DN = CLIENT_SETTINGS_NAMESPACE - .concat("x509-certificate-subject-dn"); - - private Client() { - } - - } - - /** - * The names for authorization server configuration settings. - */ - public static final class AuthorizationServer { - - private static final String AUTHORIZATION_SERVER_SETTINGS_NAMESPACE = SETTINGS_NAMESPACE - .concat("authorization-server."); - - /** - * Set the URL the Authorization Server uses as its Issuer Identifier. - */ - public static final String ISSUER = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("issuer"); - - /** - * Set to {@code true} if multiple issuers are allowed per host. - * @since 1.3 - */ - public static final String MULTIPLE_ISSUERS_ALLOWED = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("multiple-issuers-allowed"); - - /** - * Set the OAuth 2.0 Authorization endpoint. - */ - public static final String AUTHORIZATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("authorization-endpoint"); - - /** - * Set the OAuth 2.0 Pushed Authorization Request endpoint. - * @since 1.5 - */ - public static final String PUSHED_AUTHORIZATION_REQUEST_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("pushed-authorization-request-endpoint"); - - /** - * Set the OAuth 2.0 Device Authorization endpoint. - */ - public static final String DEVICE_AUTHORIZATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("device-authorization-endpoint"); - - /** - * Set the OAuth 2.0 Device Verification endpoint. - */ - public static final String DEVICE_VERIFICATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("device-verification-endpoint"); - - /** - * Set the OAuth 2.0 Token endpoint. - */ - public static final String TOKEN_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("token-endpoint"); - - /** - * Set the JWK Set endpoint. - */ - public static final String JWK_SET_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("jwk-set-endpoint"); - - /** - * Set the OAuth 2.0 Token Revocation endpoint. - */ - public static final String TOKEN_REVOCATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("token-revocation-endpoint"); - - /** - * Set the OAuth 2.0 Token Introspection endpoint. - */ - public static final String TOKEN_INTROSPECTION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("token-introspection-endpoint"); - - /** - * Set the OpenID Connect 1.0 Client Registration endpoint. - */ - public static final String OIDC_CLIENT_REGISTRATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("oidc-client-registration-endpoint"); - - /** - * Set the OpenID Connect 1.0 UserInfo endpoint. - */ - public static final String OIDC_USER_INFO_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("oidc-user-info-endpoint"); - - /** - * Set the OpenID Connect 1.0 Logout endpoint. - * @since 1.1 - */ - public static final String OIDC_LOGOUT_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE - .concat("oidc-logout-endpoint"); - - private AuthorizationServer() { - } - - } - - /** - * The names for token configuration settings. - */ - public static final class Token { - - private static final String TOKEN_SETTINGS_NAMESPACE = SETTINGS_NAMESPACE.concat("token."); - - /** - * Set the time-to-live for an authorization code. - * @since 0.4.0 - */ - public static final String AUTHORIZATION_CODE_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE - .concat("authorization-code-time-to-live"); - - /** - * Set the time-to-live for an access token. - */ - public static final String ACCESS_TOKEN_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE - .concat("access-token-time-to-live"); - - /** - * Set the {@link OAuth2TokenFormat token format} for an access token. - * @since 0.2.3 - */ - public static final String ACCESS_TOKEN_FORMAT = TOKEN_SETTINGS_NAMESPACE.concat("access-token-format"); - - /** - * Set the time-to-live for a device code. - * @since 1.1 - */ - public static final String DEVICE_CODE_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE - .concat("device-code-time-to-live"); - - /** - * Set to {@code true} if refresh tokens are reused when returning the access - * token response, or {@code false} if a new refresh token is issued. - */ - public static final String REUSE_REFRESH_TOKENS = TOKEN_SETTINGS_NAMESPACE.concat("reuse-refresh-tokens"); - - /** - * Set the time-to-live for a refresh token. - */ - public static final String REFRESH_TOKEN_TIME_TO_LIVE = TOKEN_SETTINGS_NAMESPACE - .concat("refresh-token-time-to-live"); - - /** - * Set the {@link SignatureAlgorithm JWS} algorithm for signing the - * {@link OidcIdToken ID Token}. - */ - public static final String ID_TOKEN_SIGNATURE_ALGORITHM = TOKEN_SETTINGS_NAMESPACE - .concat("id-token-signature-algorithm"); - - /** - * Set to {@code true} if access tokens must be bound to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} or {@code self_signed_tls_client_auth} method. - * @since 1.3 - */ - public static final String X509_CERTIFICATE_BOUND_ACCESS_TOKENS = TOKEN_SETTINGS_NAMESPACE - .concat("x509-certificate-bound-access-tokens"); - - private Token() { - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/OAuth2TokenFormat.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/OAuth2TokenFormat.java deleted file mode 100644 index 6cd807267..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/OAuth2TokenFormat.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.io.Serial; -import java.io.Serializable; - -import org.springframework.util.Assert; - -/** - * Standard data formats for OAuth 2.0 Tokens. - * - * @author Joe Grandja - * @since 0.2.3 - */ -public final class OAuth2TokenFormat implements Serializable { - - @Serial - private static final long serialVersionUID = -3808658977410337294L; - - /** - * Self-contained tokens use a protected, time-limited data structure that contains - * token metadata and claims of the user and/or client. JSON Web Token (JWT) is a - * widely used format. - */ - public static final OAuth2TokenFormat SELF_CONTAINED = new OAuth2TokenFormat("self-contained"); - - /** - * Reference (opaque) tokens are unique identifiers that serve as a reference to the - * token metadata and claims of the user and/or client, stored at the provider. - */ - public static final OAuth2TokenFormat REFERENCE = new OAuth2TokenFormat("reference"); - - private final String value; - - /** - * Constructs an {@code OAuth2TokenFormat} using the provided value. - * @param value the value of the token format - */ - public OAuth2TokenFormat(String value) { - Assert.hasText(value, "value cannot be empty"); - this.value = value; - } - - /** - * Returns the value of the token format. - * @return the value of the token format - */ - public String getValue() { - return this.value; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || this.getClass() != obj.getClass()) { - return false; - } - OAuth2TokenFormat that = (OAuth2TokenFormat) obj; - return getValue().equals(that.getValue()); - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java deleted file mode 100644 index 3c78cb2bf..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.io.Serial; -import java.time.Duration; -import java.util.Map; - -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.util.Assert; - -/** - * A facility for token configuration settings. - * - * @author Joe Grandja - * @since 0.0.2 - * @see AbstractSettings - * @see ConfigurationSettingNames.Token - */ -public final class TokenSettings extends AbstractSettings { - - @Serial - private static final long serialVersionUID = -2551292126445781141L; - - private TokenSettings(Map settings) { - super(settings); - } - - /** - * Returns the time-to-live for an authorization code. The default is 5 minutes. - * @return the time-to-live for an authorization code - * @since 0.4.0 - */ - public Duration getAuthorizationCodeTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE); - } - - /** - * Returns the time-to-live for an access token. The default is 5 minutes. - * @return the time-to-live for an access token - */ - public Duration getAccessTokenTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE); - } - - /** - * Returns the token format for an access token. The default is - * {@link OAuth2TokenFormat#SELF_CONTAINED}. - * @return the token format for an access token - * @since 0.2.3 - */ - public OAuth2TokenFormat getAccessTokenFormat() { - return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT); - } - - /** - * Returns the time-to-live for a device code. The default is 5 minutes. - * @return the time-to-live for a device code - * @since 1.1 - */ - public Duration getDeviceCodeTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE); - } - - /** - * Returns {@code true} if refresh tokens are reused when returning the access token - * response, or {@code false} if a new refresh token is issued. The default is - * {@code true}. - * @return {@code true} if refresh tokens are reused when returning the access token - * response, {@code false} otherwise - */ - public boolean isReuseRefreshTokens() { - return getSetting(ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS); - } - - /** - * Returns the time-to-live for a refresh token. The default is 60 minutes. - * @return the time-to-live for a refresh token - */ - public Duration getRefreshTokenTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE); - } - - /** - * Returns the {@link SignatureAlgorithm JWS} algorithm for signing the - * {@link OidcIdToken ID Token}. The default is {@link SignatureAlgorithm#RS256 - * RS256}. - * @return the {@link SignatureAlgorithm JWS} algorithm for signing the - * {@link OidcIdToken ID Token} - */ - public SignatureAlgorithm getIdTokenSignatureAlgorithm() { - return getSetting(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM); - } - - /** - * Returns {@code true} if access tokens must be bound to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} or {@code self_signed_tls_client_auth} method. The default - * is {@code false}. - * @return {@code true} if access tokens must be bound to the client - * {@code X509Certificate}, {@code false} otherwise - * @since 1.3 - */ - public boolean isX509CertificateBoundAccessTokens() { - return getSetting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS); - } - - /** - * Constructs a new {@link Builder} with the default settings. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder().authorizationCodeTimeToLive(Duration.ofMinutes(5)) - .accessTokenTimeToLive(Duration.ofMinutes(5)) - .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) - .deviceCodeTimeToLive(Duration.ofMinutes(5)) - .reuseRefreshTokens(true) - .refreshTokenTimeToLive(Duration.ofMinutes(60)) - .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256) - .x509CertificateBoundAccessTokens(false); - } - - /** - * Constructs a new {@link Builder} with the provided settings. - * @param settings the settings to initialize the builder - * @return the {@link Builder} - */ - public static Builder withSettings(Map settings) { - Assert.notEmpty(settings, "settings cannot be empty"); - return new Builder().settings((s) -> s.putAll(settings)); - } - - /** - * A builder for {@link TokenSettings}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Set the time-to-live for an authorization code. Must be greater than - * {@code Duration.ZERO}. A maximum authorization code lifetime of 10 minutes is - * RECOMMENDED. - * @param authorizationCodeTimeToLive the time-to-live for an authorization code - * @return the {@link Builder} for further configuration - * @since 0.4.0 - */ - public Builder authorizationCodeTimeToLive(Duration authorizationCodeTimeToLive) { - Assert.notNull(authorizationCodeTimeToLive, "authorizationCodeTimeToLive cannot be null"); - Assert.isTrue(authorizationCodeTimeToLive.getSeconds() > 0, - "authorizationCodeTimeToLive must be greater than Duration.ZERO"); - return setting(ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE, - authorizationCodeTimeToLive); - } - - /** - * Set the time-to-live for an access token. Must be greater than - * {@code Duration.ZERO}. - * @param accessTokenTimeToLive the time-to-live for an access token - * @return the {@link Builder} for further configuration - */ - public Builder accessTokenTimeToLive(Duration accessTokenTimeToLive) { - Assert.notNull(accessTokenTimeToLive, "accessTokenTimeToLive cannot be null"); - Assert.isTrue(accessTokenTimeToLive.getSeconds() > 0, - "accessTokenTimeToLive must be greater than Duration.ZERO"); - return setting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive); - } - - /** - * Set the token format for an access token. - * @param accessTokenFormat the token format for an access token - * @return the {@link Builder} for further configuration - * @since 0.2.3 - */ - public Builder accessTokenFormat(OAuth2TokenFormat accessTokenFormat) { - Assert.notNull(accessTokenFormat, "accessTokenFormat cannot be null"); - return setting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT, accessTokenFormat); - } - - /** - * Set the time-to-live for a device code. Must be greater than - * {@code Duration.ZERO}. - * @param deviceCodeTimeToLive the time-to-live for a device code - * @return the {@link Builder} for further configuration - * @since 1.1 - */ - public Builder deviceCodeTimeToLive(Duration deviceCodeTimeToLive) { - Assert.notNull(deviceCodeTimeToLive, "deviceCodeTimeToLive cannot be null"); - Assert.isTrue(deviceCodeTimeToLive.getSeconds() > 0, - "deviceCodeTimeToLive must be greater than Duration.ZERO"); - return setting(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE, deviceCodeTimeToLive); - } - - /** - * Set to {@code true} if refresh tokens are reused when returning the access - * token response, or {@code false} if a new refresh token is issued. - * @param reuseRefreshTokens {@code true} to reuse refresh tokens, {@code false} - * to issue new refresh tokens - * @return the {@link Builder} for further configuration - */ - public Builder reuseRefreshTokens(boolean reuseRefreshTokens) { - return setting(ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS, reuseRefreshTokens); - } - - /** - * Set the time-to-live for a refresh token. Must be greater than - * {@code Duration.ZERO}. - * @param refreshTokenTimeToLive the time-to-live for a refresh token - * @return the {@link Builder} for further configuration - */ - public Builder refreshTokenTimeToLive(Duration refreshTokenTimeToLive) { - Assert.notNull(refreshTokenTimeToLive, "refreshTokenTimeToLive cannot be null"); - Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, - "refreshTokenTimeToLive must be greater than Duration.ZERO"); - return setting(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE, refreshTokenTimeToLive); - } - - /** - * Sets the {@link SignatureAlgorithm JWS} algorithm for signing the - * {@link OidcIdToken ID Token}. - * @param idTokenSignatureAlgorithm the {@link SignatureAlgorithm JWS} algorithm - * for signing the {@link OidcIdToken ID Token} - * @return the {@link Builder} for further configuration - */ - public Builder idTokenSignatureAlgorithm(SignatureAlgorithm idTokenSignatureAlgorithm) { - Assert.notNull(idTokenSignatureAlgorithm, "idTokenSignatureAlgorithm cannot be null"); - return setting(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM, idTokenSignatureAlgorithm); - } - - /** - * Set to {@code true} if access tokens must be bound to the client - * {@code X509Certificate} received during client authentication when using the - * {@code tls_client_auth} or {@code self_signed_tls_client_auth} method. - * @param x509CertificateBoundAccessTokens {@code true} if access tokens must be - * bound to the client {@code X509Certificate}, {@code false} otherwise - * @return the {@link Builder} for further configuration - * @since 1.3 - */ - public Builder x509CertificateBoundAccessTokens(boolean x509CertificateBoundAccessTokens) { - return setting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS, - x509CertificateBoundAccessTokens); - } - - /** - * Builds the {@link TokenSettings}. - * @return the {@link TokenSettings} - */ - @Override - public TokenSettings build() { - return new TokenSettings(getSettings()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java deleted file mode 100644 index 547dfff55..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link OAuth2TokenContext}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenContext - */ -public final class DefaultOAuth2TokenContext implements OAuth2TokenContext { - - private final Map context; - - private DefaultOAuth2TokenContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns a new {@link Builder}. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for {@link DefaultOAuth2TokenContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder() { - } - - /** - * Builds a new {@link DefaultOAuth2TokenContext}. - * @return the {@link DefaultOAuth2TokenContext} - */ - @Override - public DefaultOAuth2TokenContext build() { - return new DefaultOAuth2TokenContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java deleted file mode 100644 index b3caaf271..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2TokenGenerator} that simply delegates to it's internal {@code List} of - * {@link OAuth2TokenGenerator}(s). - *

- * Each {@link OAuth2TokenGenerator} is given a chance to - * {@link OAuth2TokenGenerator#generate(OAuth2TokenContext)} with the first - * {@code non-null} {@link OAuth2Token} being returned. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenGenerator - * @see JwtGenerator - * @see OAuth2RefreshTokenGenerator - */ -public final class DelegatingOAuth2TokenGenerator implements OAuth2TokenGenerator { - - private final List> tokenGenerators; - - /** - * Constructs a {@code DelegatingOAuth2TokenGenerator} using the provided parameters. - * @param tokenGenerators an array of {@link OAuth2TokenGenerator}(s) - */ - @SafeVarargs - public DelegatingOAuth2TokenGenerator(OAuth2TokenGenerator... tokenGenerators) { - Assert.notEmpty(tokenGenerators, "tokenGenerators cannot be empty"); - Assert.noNullElements(tokenGenerators, "tokenGenerator cannot be null"); - this.tokenGenerators = Collections.unmodifiableList(asList(tokenGenerators)); - } - - @Nullable - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - for (OAuth2TokenGenerator tokenGenerator : this.tokenGenerators) { - OAuth2Token token = tokenGenerator.generate(context); - if (token != null) { - return token; - } - } - return null; - } - - @SuppressWarnings("unchecked") - private static List> asList( - OAuth2TokenGenerator... tokenGenerators) { - - List> tokenGeneratorList = new ArrayList<>(); - for (OAuth2TokenGenerator tokenGenerator : tokenGenerators) { - tokenGeneratorList.add((OAuth2TokenGenerator) tokenGenerator); - } - return tokenGeneratorList; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java deleted file mode 100644 index 0debe862d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2TokenContext} implementation used when encoding a {@link Jwt}. - * - * @author Joe Grandja - * @since 0.1.0 - * @see OAuth2TokenContext - * @see JwsHeader.Builder - * @see JwtClaimsSet.Builder - * @see JwtEncoder#encode(JwtEncoderParameters) - */ -public final class JwtEncodingContext implements OAuth2TokenContext { - - private final Map context; - - private JwtEncodingContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link JwsHeader.Builder JWS headers} allowing the ability to add, - * replace, or remove. - * @return the {@link JwsHeader.Builder} - */ - public JwsHeader.Builder getJwsHeader() { - return get(JwsHeader.Builder.class); - } - - /** - * Returns the {@link JwtClaimsSet.Builder claims} allowing the ability to add, - * replace, or remove. - * @return the {@link JwtClaimsSet.Builder} - */ - public JwtClaimsSet.Builder getClaims() { - return get(JwtClaimsSet.Builder.class); - } - - /** - * Constructs a new {@link Builder} with the provided JWS headers and claims. - * @param jwsHeaderBuilder the JWS headers to initialize the builder - * @param claimsBuilder the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder with(JwsHeader.Builder jwsHeaderBuilder, JwtClaimsSet.Builder claimsBuilder) { - return new Builder(jwsHeaderBuilder, claimsBuilder); - } - - /** - * A builder for {@link JwtEncodingContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(JwsHeader.Builder jwsHeaderBuilder, JwtClaimsSet.Builder claimsBuilder) { - Assert.notNull(jwsHeaderBuilder, "jwsHeaderBuilder cannot be null"); - Assert.notNull(claimsBuilder, "claimsBuilder cannot be null"); - put(JwsHeader.Builder.class, jwsHeaderBuilder); - put(JwtClaimsSet.Builder.class, claimsBuilder); - } - - /** - * Builds a new {@link JwtEncodingContext}. - * @return the {@link JwtEncodingContext} - */ - @Override - public JwtEncodingContext build() { - return new JwtEncodingContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java deleted file mode 100644 index 249c86e3b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Date; -import java.util.UUID; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link OAuth2TokenGenerator} that generates a {@link Jwt} used for an - * {@link OAuth2AccessToken} or {@link OidcIdToken}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenGenerator - * @see Jwt - * @see JwtEncoder - * @see OAuth2TokenCustomizer - * @see JwtEncodingContext - * @see OAuth2AccessToken - * @see OidcIdToken - */ -public final class JwtGenerator implements OAuth2TokenGenerator { - - private final JwtEncoder jwtEncoder; - - private OAuth2TokenCustomizer jwtCustomizer; - - /** - * Constructs a {@code JwtGenerator} using the provided parameters. - * @param jwtEncoder the jwt encoder - */ - public JwtGenerator(JwtEncoder jwtEncoder) { - Assert.notNull(jwtEncoder, "jwtEncoder cannot be null"); - this.jwtEncoder = jwtEncoder; - } - - @Nullable - @Override - public Jwt generate(OAuth2TokenContext context) { - // @formatter:off - if (context.getTokenType() == null || - (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && - !OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue()))) { - return null; - } - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && - !OAuth2TokenFormat.SELF_CONTAINED.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { - return null; - } - // @formatter:on - - String issuer = null; - if (context.getAuthorizationServerContext() != null) { - issuer = context.getAuthorizationServerContext().getIssuer(); - } - RegisteredClient registeredClient = context.getRegisteredClient(); - - Instant issuedAt = Instant.now(); - Instant expiresAt; - JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256; - if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - // TODO Allow configuration for ID Token time-to-live - expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - if (registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) { - jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm(); - } - } - else { - expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); - } - - // @formatter:off - JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder(); - if (StringUtils.hasText(issuer)) { - claimsBuilder.issuer(issuer); - } - claimsBuilder - .subject(context.getPrincipal().getName()) - .audience(Collections.singletonList(registeredClient.getClientId())) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .id(UUID.randomUUID().toString()); - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - claimsBuilder.notBefore(issuedAt); - if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) { - claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes()); - } - } - else if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - claimsBuilder.claim(IdTokenClaimNames.AZP, registeredClient.getClientId()); - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType())) { - OAuth2AuthorizationRequest authorizationRequest = context.getAuthorization().getAttribute( - OAuth2AuthorizationRequest.class.getName()); - String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE); - if (StringUtils.hasText(nonce)) { - claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce); - } - SessionInformation sessionInformation = context.get(SessionInformation.class); - if (sessionInformation != null) { - claimsBuilder.claim("sid", sessionInformation.getSessionId()); - claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, sessionInformation.getLastRequest()); - } - } - else if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { - OidcIdToken currentIdToken = context.getAuthorization().getToken(OidcIdToken.class).getToken(); - if (currentIdToken.hasClaim("sid")) { - claimsBuilder.claim("sid", currentIdToken.getClaim("sid")); - } - if (currentIdToken.hasClaim(IdTokenClaimNames.AUTH_TIME)) { - claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, currentIdToken.getClaim(IdTokenClaimNames.AUTH_TIME)); - } - } - } - // @formatter:on - - JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm); - - if (this.jwtCustomizer != null) { - // @formatter:off - JwtEncodingContext.Builder jwtContextBuilder = JwtEncodingContext.with(jwsHeaderBuilder, claimsBuilder) - .registeredClient(context.getRegisteredClient()) - .principal(context.getPrincipal()) - .authorizationServerContext(context.getAuthorizationServerContext()) - .authorizedScopes(context.getAuthorizedScopes()) - .tokenType(context.getTokenType()) - .authorizationGrantType(context.getAuthorizationGrantType()); - if (context.getAuthorization() != null) { - jwtContextBuilder.authorization(context.getAuthorization()); - } - if (context.getAuthorizationGrant() != null) { - jwtContextBuilder.authorizationGrant(context.getAuthorizationGrant()); - } - if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - SessionInformation sessionInformation = context.get(SessionInformation.class); - if (sessionInformation != null) { - jwtContextBuilder.put(SessionInformation.class, sessionInformation); - } - } - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - Jwt dPoPProofJwt = context.get(OAuth2TokenContext.DPOP_PROOF_KEY); - if (dPoPProofJwt != null) { - jwtContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProofJwt); - } - } - // @formatter:on - - JwtEncodingContext jwtContext = jwtContextBuilder.build(); - this.jwtCustomizer.customize(jwtContext); - } - - JwsHeader jwsHeader = jwsHeaderBuilder.build(); - JwtClaimsSet claims = claimsBuilder.build(); - - Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - - return jwt; - } - - /** - * Sets the {@link OAuth2TokenCustomizer} that customizes the - * {@link JwtEncodingContext#getJwsHeader() JWS headers} and/or - * {@link JwtEncodingContext#getClaims() claims} for the generated {@link Jwt}. - * @param jwtCustomizer the {@link OAuth2TokenCustomizer} that customizes the headers - * and/or claims for the generated {@code Jwt} - */ - public void setJwtCustomizer(OAuth2TokenCustomizer jwtCustomizer) { - Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null"); - this.jwtCustomizer = jwtCustomizer; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java deleted file mode 100644 index 4f03c166c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.time.Instant; -import java.util.Base64; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.springframework.lang.Nullable; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * An {@link OAuth2TokenGenerator} that generates a {@link OAuth2TokenFormat#REFERENCE - * "reference"} (opaque) {@link OAuth2AccessToken}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenGenerator - * @see OAuth2AccessToken - * @see OAuth2TokenCustomizer - * @see OAuth2TokenClaimsContext - * @see OAuth2TokenClaimsSet - */ -public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - private OAuth2TokenCustomizer accessTokenCustomizer; - - @Nullable - @Override - public OAuth2AccessToken generate(OAuth2TokenContext context) { - // @formatter:off - if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || - !OAuth2TokenFormat.REFERENCE.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { - return null; - } - // @formatter:on - - String issuer = null; - if (context.getAuthorizationServerContext() != null) { - issuer = context.getAuthorizationServerContext().getIssuer(); - } - RegisteredClient registeredClient = context.getRegisteredClient(); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); - - // @formatter:off - OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder(); - if (StringUtils.hasText(issuer)) { - claimsBuilder.issuer(issuer); - } - claimsBuilder - .subject(context.getPrincipal().getName()) - .audience(Collections.singletonList(registeredClient.getClientId())) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .notBefore(issuedAt) - .id(UUID.randomUUID().toString()); - if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) { - claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes()); - } - // @formatter:on - - if (this.accessTokenCustomizer != null) { - // @formatter:off - OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder) - .registeredClient(context.getRegisteredClient()) - .principal(context.getPrincipal()) - .authorizationServerContext(context.getAuthorizationServerContext()) - .authorizedScopes(context.getAuthorizedScopes()) - .tokenType(context.getTokenType()) - .authorizationGrantType(context.getAuthorizationGrantType()); - if (context.getAuthorization() != null) { - accessTokenContextBuilder.authorization(context.getAuthorization()); - } - if (context.getAuthorizationGrant() != null) { - accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant()); - } - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - Jwt dPoPProofJwt = context.get(OAuth2TokenContext.DPOP_PROOF_KEY); - if (dPoPProofJwt != null) { - accessTokenContextBuilder.put(OAuth2TokenContext.DPOP_PROOF_KEY, dPoPProofJwt); - } - } - // @formatter:on - - OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build(); - this.accessTokenCustomizer.customize(accessTokenContext); - } - - OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build(); - - OAuth2AccessToken accessToken = new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, - this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(), - accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims()); - - return accessToken; - } - - /** - * Sets the {@link OAuth2TokenCustomizer} that customizes the - * {@link OAuth2TokenClaimsContext#getClaims() claims} for the - * {@link OAuth2AccessToken}. - * @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the - * claims for the {@code OAuth2AccessToken} - */ - public void setAccessTokenCustomizer(OAuth2TokenCustomizer accessTokenCustomizer) { - Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null"); - this.accessTokenCustomizer = accessTokenCustomizer; - } - - private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor { - - private final Map claims; - - private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, - Set scopes, Map claims) { - super(tokenType, tokenValue, issuedAt, expiresAt, scopes); - this.claims = claims; - } - - @Override - public Map getClaims() { - return this.claims; - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java deleted file mode 100644 index 8c5855aac..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.token; - -import java.time.Instant; -import java.util.Base64; - -import org.springframework.lang.Nullable; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; - -/** - * An {@link OAuth2TokenGenerator} that generates an {@link OAuth2RefreshToken}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenGenerator - * @see OAuth2RefreshToken - */ -public final class OAuth2RefreshTokenGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator refreshTokenGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - @Nullable - @Override - public OAuth2RefreshToken generate(OAuth2TokenContext context) { - if (!OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { - return null; - } - if (isPublicClientForAuthorizationCodeGrant(context)) { - // Do not issue refresh token to public client - return null; - } - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); - return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt); - } - - private static boolean isPublicClientForAuthorizationCodeGrant(OAuth2TokenContext context) { - // @formatter:off - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) && - (context.getAuthorizationGrant().getPrincipal() instanceof OAuth2ClientAuthenticationToken clientPrincipal)) { - return clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE); - } - // @formatter:on - return false; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimAccessor.java deleted file mode 100644 index 18f6c9967..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimAccessor.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.net.URL; -import java.time.Instant; -import java.util.List; - -import org.springframework.security.oauth2.core.ClaimAccessor; - -/** - * A {@link ClaimAccessor} for the "claims" that may be contained in an - * {@link OAuth2TokenClaimsSet}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see ClaimAccessor - * @see OAuth2TokenClaimNames - * @see OAuth2TokenClaimsSet - */ -public interface OAuth2TokenClaimAccessor extends ClaimAccessor { - - /** - * Returns the Issuer {@code (iss)} claim which identifies the principal that issued - * the OAuth 2.0 Token. - * @return the Issuer identifier - */ - default URL getIssuer() { - return getClaimAsURL(OAuth2TokenClaimNames.ISS); - } - - /** - * Returns the Subject {@code (sub)} claim which identifies the principal that is the - * subject of the OAuth 2.0 Token. - * @return the Subject identifier - */ - default String getSubject() { - return getClaimAsString(OAuth2TokenClaimNames.SUB); - } - - /** - * Returns the Audience {@code (aud)} claim which identifies the recipient(s) that the - * OAuth 2.0 Token is intended for. - * @return the Audience(s) that this OAuth 2.0 Token is intended for - */ - default List getAudience() { - return getClaimAsStringList(OAuth2TokenClaimNames.AUD); - } - - /** - * Returns the Expiration time {@code (exp)} claim which identifies the expiration - * time on or after which the OAuth 2.0 Token MUST NOT be accepted for processing. - * @return the Expiration time on or after which the OAuth 2.0 Token MUST NOT be - * accepted for processing - */ - default Instant getExpiresAt() { - return getClaimAsInstant(OAuth2TokenClaimNames.EXP); - } - - /** - * Returns the Not Before {@code (nbf)} claim which identifies the time before which - * the OAuth 2.0 Token MUST NOT be accepted for processing. - * @return the Not Before time before which the OAuth 2.0 Token MUST NOT be accepted - * for processing - */ - default Instant getNotBefore() { - return getClaimAsInstant(OAuth2TokenClaimNames.NBF); - } - - /** - * Returns the Issued at {@code (iat)} claim which identifies the time at which the - * OAuth 2.0 Token was issued. - * @return the Issued at claim which identifies the time at which the OAuth 2.0 Token - * was issued - */ - default Instant getIssuedAt() { - return getClaimAsInstant(OAuth2TokenClaimNames.IAT); - } - - /** - * Returns the ID {@code (jti)} claim which provides a unique identifier for the OAuth - * 2.0 Token. - * @return the ID claim which provides a unique identifier for the OAuth 2.0 Token - */ - default String getId() { - return getClaimAsString(OAuth2TokenClaimNames.JTI); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimNames.java deleted file mode 100644 index beaf0a42c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimNames.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import org.springframework.security.oauth2.core.OAuth2Token; - -/** - * The names of the "claims" that may be contained in an {@link OAuth2TokenClaimsSet} and - * are associated to an {@link OAuth2Token}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenClaimAccessor - * @see OAuth2TokenClaimsSet - * @see OAuth2Token - */ -public final class OAuth2TokenClaimNames { - - /** - * {@code iss} - the Issuer claim identifies the principal that issued the OAuth 2.0 - * Token - */ - public static final String ISS = "iss"; - - /** - * {@code sub} - the Subject claim identifies the principal that is the subject of the - * OAuth 2.0 Token - */ - public static final String SUB = "sub"; - - /** - * {@code aud} - the Audience claim identifies the recipient(s) that the OAuth 2.0 - * Token is intended for - */ - public static final String AUD = "aud"; - - /** - * {@code exp} - the Expiration time claim identifies the expiration time on or after - * which the OAuth 2.0 Token MUST NOT be accepted for processing - */ - public static final String EXP = "exp"; - - /** - * {@code nbf} - the Not Before claim identifies the time before which the OAuth 2.0 - * Token MUST NOT be accepted for processing - */ - public static final String NBF = "nbf"; - - /** - * {@code iat} - The Issued at claim identifies the time at which the OAuth 2.0 Token - * was issued - */ - public static final String IAT = "iat"; - - /** - * {@code jti} - The ID claim provides a unique identifier for the OAuth 2.0 Token - */ - public static final String JTI = "jti"; - - private OAuth2TokenClaimNames() { - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java deleted file mode 100644 index 9425e840d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * An {@link OAuth2TokenContext} implementation that provides access to the - * {@link #getClaims() claims} of an OAuth 2.0 Token, allowing the ability to customize. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenContext - * @see OAuth2TokenClaimsSet.Builder - */ -public final class OAuth2TokenClaimsContext implements OAuth2TokenContext { - - private final Map context; - - private OAuth2TokenClaimsContext(Map context) { - this.context = Collections.unmodifiableMap(new HashMap<>(context)); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public V get(Object key) { - return hasKey(key) ? (V) this.context.get(key) : null; - } - - @Override - public boolean hasKey(Object key) { - Assert.notNull(key, "key cannot be null"); - return this.context.containsKey(key); - } - - /** - * Returns the {@link OAuth2TokenClaimsSet.Builder claims} allowing the ability to - * add, replace, or remove. - * @return the {@link OAuth2TokenClaimsSet.Builder} - */ - public OAuth2TokenClaimsSet.Builder getClaims() { - return get(OAuth2TokenClaimsSet.Builder.class); - } - - /** - * Constructs a new {@link Builder} with the provided claims. - * @param claimsBuilder the claims to initialize the builder - * @return the {@link Builder} - */ - public static Builder with(OAuth2TokenClaimsSet.Builder claimsBuilder) { - return new Builder(claimsBuilder); - } - - /** - * A builder for {@link OAuth2TokenClaimsContext}. - */ - public static final class Builder extends AbstractBuilder { - - private Builder(OAuth2TokenClaimsSet.Builder claimsBuilder) { - Assert.notNull(claimsBuilder, "claimsBuilder cannot be null"); - put(OAuth2TokenClaimsSet.Builder.class, claimsBuilder); - } - - /** - * Builds a new {@link OAuth2TokenClaimsContext}. - * @return the {@link OAuth2TokenClaimsContext} - */ - @Override - public OAuth2TokenClaimsContext build() { - return new OAuth2TokenClaimsContext(getContext()); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSet.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSet.java deleted file mode 100644 index c168b808b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSet.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.net.URL; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.converter.ClaimConversionService; -import org.springframework.util.Assert; - -/** - * A representation of a set of claims that are associated to an {@link OAuth2Token}. - * - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2TokenClaimAccessor - * @see OAuth2TokenClaimNames - * @see OAuth2Token - */ -public final class OAuth2TokenClaimsSet implements OAuth2TokenClaimAccessor { - - private final Map claims; - - private OAuth2TokenClaimsSet(Map claims) { - this.claims = Collections.unmodifiableMap(new HashMap<>(claims)); - } - - @Override - public Map getClaims() { - return this.claims; - } - - /** - * Returns a new {@link Builder}. - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for {@link OAuth2TokenClaimsSet}. - */ - public static final class Builder { - - private final Map claims = new HashMap<>(); - - private Builder() { - } - - /** - * Sets the issuer {@code (iss)} claim, which identifies the principal that issued - * the OAuth 2.0 Token. - * @param issuer the issuer identifier - * @return the {@link Builder} - */ - public Builder issuer(String issuer) { - return claim(OAuth2TokenClaimNames.ISS, issuer); - } - - /** - * Sets the subject {@code (sub)} claim, which identifies the principal that is - * the subject of the OAuth 2.0 Token. - * @param subject the subject identifier - * @return the {@link Builder} - */ - public Builder subject(String subject) { - return claim(OAuth2TokenClaimNames.SUB, subject); - } - - /** - * Sets the audience {@code (aud)} claim, which identifies the recipient(s) that - * the OAuth 2.0 Token is intended for. - * @param audience the audience that this OAuth 2.0 Token is intended for - * @return the {@link Builder} - */ - public Builder audience(List audience) { - return claim(OAuth2TokenClaimNames.AUD, audience); - } - - /** - * Sets the expiration time {@code (exp)} claim, which identifies the time on or - * after which the OAuth 2.0 Token MUST NOT be accepted for processing. - * @param expiresAt the time on or after which the OAuth 2.0 Token MUST NOT be - * accepted for processing - * @return the {@link Builder} - */ - public Builder expiresAt(Instant expiresAt) { - return claim(OAuth2TokenClaimNames.EXP, expiresAt); - } - - /** - * Sets the not before {@code (nbf)} claim, which identifies the time before which - * the OAuth 2.0 Token MUST NOT be accepted for processing. - * @param notBefore the time before which the OAuth 2.0 Token MUST NOT be accepted - * for processing - * @return the {@link Builder} - */ - public Builder notBefore(Instant notBefore) { - return claim(OAuth2TokenClaimNames.NBF, notBefore); - } - - /** - * Sets the issued at {@code (iat)} claim, which identifies the time at which the - * OAuth 2.0 Token was issued. - * @param issuedAt the time at which the OAuth 2.0 Token was issued - * @return the {@link Builder} - */ - public Builder issuedAt(Instant issuedAt) { - return claim(OAuth2TokenClaimNames.IAT, issuedAt); - } - - /** - * Sets the ID {@code (jti)} claim, which provides a unique identifier for the - * OAuth 2.0 Token. - * @param jti the unique identifier for the OAuth 2.0 Token - * @return the {@link Builder} - */ - public Builder id(String jti) { - return claim(OAuth2TokenClaimNames.JTI, jti); - } - - /** - * Sets the claim. - * @param name the claim name - * @param value the claim value - * @return the {@link Builder} - */ - public Builder claim(String name, Object value) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(value, "value cannot be null"); - this.claims.put(name, value); - return this; - } - - /** - * A {@code Consumer} to be provided access to the claims allowing the ability to - * add, replace, or remove. - * @param claimsConsumer a {@code Consumer} of the claims - * @return the {@link Builder} - */ - public Builder claims(Consumer> claimsConsumer) { - claimsConsumer.accept(this.claims); - return this; - } - - /** - * Builds a new {@link OAuth2TokenClaimsSet}. - * @return a {@link OAuth2TokenClaimsSet} - */ - public OAuth2TokenClaimsSet build() { - Assert.notEmpty(this.claims, "claims cannot be empty"); - - // The value of the 'iss' claim is a String or URL (StringOrURI). - // Attempt to convert to URL. - Object issuer = this.claims.get(OAuth2TokenClaimNames.ISS); - if (issuer != null) { - URL convertedValue = ClaimConversionService.getSharedInstance().convert(issuer, URL.class); - if (convertedValue != null) { - this.claims.put(OAuth2TokenClaimNames.ISS, convertedValue); - } - } - - return new OAuth2TokenClaimsSet(this.claims); - } - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java deleted file mode 100644 index 344fc410e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.token; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.Context; -import org.springframework.util.Assert; - -/** - * A context that holds information (to be) associated to an OAuth 2.0 Token and is used - * by an {@link OAuth2TokenGenerator} and {@link OAuth2TokenCustomizer}. - * - * @author Joe Grandja - * @since 0.1.0 - * @see Context - * @see OAuth2TokenGenerator - * @see OAuth2TokenCustomizer - */ -public interface OAuth2TokenContext extends Context { - - /** - * The key used for the DPoP Proof {@link Jwt} (if available). - * @since 1.5 - */ - String DPOP_PROOF_KEY = Jwt.class.getName().concat(".DPOP_PROOF"); - - /** - * Returns the {@link RegisteredClient registered client}. - * @return the {@link RegisteredClient} - */ - default RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); - } - - /** - * Returns the {@link Authentication} representing the {@code Principal} resource - * owner (or client). - * @param the type of the {@code Authentication} - * @return the {@link Authentication} representing the {@code Principal} resource - * owner (or client) - */ - default T getPrincipal() { - return get(AbstractBuilder.PRINCIPAL_AUTHENTICATION_KEY); - } - - /** - * Returns the {@link AuthorizationServerContext authorization server context}. - * @return the {@link AuthorizationServerContext} - * @since 0.2.3 - */ - default AuthorizationServerContext getAuthorizationServerContext() { - return get(AuthorizationServerContext.class); - } - - /** - * Returns the {@link OAuth2Authorization authorization}. - * @return the {@link OAuth2Authorization}, or {@code null} if not available - */ - @Nullable - default OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); - } - - /** - * Returns the authorized scope(s). - * @return the authorized scope(s) - */ - default Set getAuthorizedScopes() { - return hasKey(AbstractBuilder.AUTHORIZED_SCOPE_KEY) ? get(AbstractBuilder.AUTHORIZED_SCOPE_KEY) - : Collections.emptySet(); - } - - /** - * Returns the {@link OAuth2TokenType token type}. - * @return the {@link OAuth2TokenType} - */ - default OAuth2TokenType getTokenType() { - return get(OAuth2TokenType.class); - } - - /** - * Returns the {@link AuthorizationGrantType authorization grant type}. - * @return the {@link AuthorizationGrantType} - */ - default AuthorizationGrantType getAuthorizationGrantType() { - return get(AuthorizationGrantType.class); - } - - /** - * Returns the {@link Authentication} representing the authorization grant. - * @param the type of the {@code Authentication} - * @return the {@link Authentication} representing the authorization grant - */ - default T getAuthorizationGrant() { - return get(AbstractBuilder.AUTHORIZATION_GRANT_AUTHENTICATION_KEY); - } - - /** - * Base builder for implementations of {@link OAuth2TokenContext}. - * - * @param the type of the context - * @param the type of the builder - */ - abstract class AbstractBuilder> { - - private static final String PRINCIPAL_AUTHENTICATION_KEY = Authentication.class.getName().concat(".PRINCIPAL"); - - private static final String AUTHORIZED_SCOPE_KEY = OAuth2Authorization.class.getName() - .concat(".AUTHORIZED_SCOPE"); - - private static final String AUTHORIZATION_GRANT_AUTHENTICATION_KEY = Authentication.class.getName() - .concat(".AUTHORIZATION_GRANT"); - - private final Map context = new HashMap<>(); - - /** - * Sets the {@link RegisteredClient registered client}. - * @param registeredClient the {@link RegisteredClient} - * @return the {@link AbstractBuilder} for further configuration - */ - public B registeredClient(RegisteredClient registeredClient) { - return put(RegisteredClient.class, registeredClient); - } - - /** - * Sets the {@link Authentication} representing the {@code Principal} resource - * owner (or client). - * @param principal the {@link Authentication} representing the {@code Principal} - * resource owner (or client) - * @return the {@link AbstractBuilder} for further configuration - */ - public B principal(Authentication principal) { - return put(PRINCIPAL_AUTHENTICATION_KEY, principal); - } - - /** - * Sets the {@link AuthorizationServerContext authorization server context}. - * @param authorizationServerContext the {@link AuthorizationServerContext} - * @return the {@link AbstractBuilder} for further configuration - * @since 0.2.3 - */ - public B authorizationServerContext(AuthorizationServerContext authorizationServerContext) { - return put(AuthorizationServerContext.class, authorizationServerContext); - } - - /** - * Sets the {@link OAuth2Authorization authorization}. - * @param authorization the {@link OAuth2Authorization} - * @return the {@link AbstractBuilder} for further configuration - */ - public B authorization(OAuth2Authorization authorization) { - return put(OAuth2Authorization.class, authorization); - } - - /** - * Sets the authorized scope(s). - * @param authorizedScopes the authorized scope(s) - * @return the {@link AbstractBuilder} for further configuration - */ - public B authorizedScopes(Set authorizedScopes) { - return put(AUTHORIZED_SCOPE_KEY, authorizedScopes); - } - - /** - * Sets the {@link OAuth2TokenType token type}. - * @param tokenType the {@link OAuth2TokenType} - * @return the {@link AbstractBuilder} for further configuration - */ - public B tokenType(OAuth2TokenType tokenType) { - return put(OAuth2TokenType.class, tokenType); - } - - /** - * Sets the {@link AuthorizationGrantType authorization grant type}. - * @param authorizationGrantType the {@link AuthorizationGrantType} - * @return the {@link AbstractBuilder} for further configuration - */ - public B authorizationGrantType(AuthorizationGrantType authorizationGrantType) { - return put(AuthorizationGrantType.class, authorizationGrantType); - } - - /** - * Sets the {@link Authentication} representing the authorization grant. - * @param authorizationGrant the {@link Authentication} representing the - * authorization grant - * @return the {@link AbstractBuilder} for further configuration - */ - public B authorizationGrant(Authentication authorizationGrant) { - return put(AUTHORIZATION_GRANT_AUTHENTICATION_KEY, authorizationGrant); - } - - /** - * Associates an attribute. - * @param key the key for the attribute - * @param value the value of the attribute - * @return the {@link AbstractBuilder} for further configuration - */ - public B put(Object key, Object value) { - Assert.notNull(key, "key cannot be null"); - Assert.notNull(value, "value cannot be null"); - this.context.put(key, value); - return getThis(); - } - - /** - * A {@code Consumer} of the attributes {@code Map} allowing the ability to add, - * replace, or remove. - * @param contextConsumer a {@link Consumer} of the attributes {@code Map} - * @return the {@link AbstractBuilder} for further configuration - */ - public B context(Consumer> contextConsumer) { - contextConsumer.accept(this.context); - return getThis(); - } - - @SuppressWarnings("unchecked") - protected V get(Object key) { - return (V) this.context.get(key); - } - - protected Map getContext() { - return this.context; - } - - @SuppressWarnings("unchecked") - protected final B getThis() { - return (B) this; - } - - /** - * Builds a new {@link OAuth2TokenContext}. - * @return the {@link OAuth2TokenContext} - */ - public abstract T build(); - - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenCustomizer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenCustomizer.java deleted file mode 100644 index 4403e7b28..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenCustomizer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -/** - * Implementations of this interface are responsible for customizing the OAuth 2.0 Token - * attributes contained within the {@link OAuth2TokenContext}. - * - * @param the type of the context containing the OAuth 2.0 Token attributes - * @author Joe Grandja - * @since 0.1.0 - * @see OAuth2TokenContext - */ -@FunctionalInterface -public interface OAuth2TokenCustomizer { - - /** - * Customize the OAuth 2.0 Token attributes. - * @param context the context containing the OAuth 2.0 Token attributes - */ - void customize(T context); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java deleted file mode 100644 index f4fe04668..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; - -/** - * Implementations of this interface are responsible for generating an {@link OAuth2Token} - * using the attributes contained in the {@link OAuth2TokenContext}. - * - * @param the type of the OAuth 2.0 Token - * @author Joe Grandja - * @since 0.2.3 - * @see OAuth2Token - * @see OAuth2TokenContext - * @see OAuth2TokenClaimsSet - * @see ClaimAccessor - */ -@FunctionalInterface -public interface OAuth2TokenGenerator { - - /** - * Generate an OAuth 2.0 Token using the attributes contained in the - * {@link OAuth2TokenContext}, or return {@code null} if the - * {@link OAuth2TokenContext#getTokenType()} is not supported. - * - *

- * If the returned {@link OAuth2Token} has a set of claims, it should implement - * {@link ClaimAccessor} in order for it to be stored with the - * {@link OAuth2Authorization}. - * @param context the context containing the OAuth 2.0 Token attributes - * @return an {@link OAuth2Token} or {@code null} if the - * {@link OAuth2TokenContext#getTokenType()} is not supported - */ - @Nullable - T generate(OAuth2TokenContext context); - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/DefaultConsentPage.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/DefaultConsentPage.java deleted file mode 100644 index bc11f5fd2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/DefaultConsentPage.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; - -/** - * For internal use only. - * - * @author Joe Grandja - */ -final class DefaultConsentPage { - - private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8); - - private DefaultConsentPage() { - } - - static void displayConsent(HttpServletRequest request, HttpServletResponse response, String clientId, - Authentication principal, Set requestedScopes, Set authorizedScopes, String state, - Map additionalParameters) throws IOException { - - String consentPage = generateConsentPage(request, clientId, principal, requestedScopes, authorizedScopes, state, - additionalParameters); - response.setContentType(TEXT_HTML_UTF8.toString()); - response.setContentLength(consentPage.getBytes(StandardCharsets.UTF_8).length); - response.getWriter().write(consentPage); - } - - private static String generateConsentPage(HttpServletRequest request, String clientId, Authentication principal, - Set requestedScopes, Set authorizedScopes, String state, - Map additionalParameters) { - Set scopesToAuthorize = new HashSet<>(); - Set scopesPreviouslyAuthorized = new HashSet<>(); - for (String scope : requestedScopes) { - if (authorizedScopes.contains(scope)) { - scopesPreviouslyAuthorized.add(scope); - } - else if (!scope.equals(OidcScopes.OPENID)) { - // openid scope does not require consent - scopesToAuthorize.add(scope); - } - } - - // https://datatracker.ietf.org/doc/html/rfc8628#section-3.3.1 - // The server SHOULD display - // the "user_code" to the user and ask them to verify that it matches - // the "user_code" being displayed on the device to confirm they are - // authorizing the correct device. - String userCode = additionalParameters.get(OAuth2ParameterNames.USER_CODE); - - // @formatter:off - StringBuilder builder = new StringBuilder(); - builder.append(""); - builder.append(""); - builder.append(""); - builder.append(" "); - builder.append(" "); - builder.append(" "); - builder.append(" Consent required"); - builder.append(" "); - builder.append(""); - builder.append(""); - builder.append("

"); - builder.append("
"); - builder.append("

Consent required

"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("

" + clientId + " wants to access your account " + principal.getName() + "

"); - builder.append("
"); - builder.append("
"); - if (userCode != null) { - builder.append("
"); - builder.append("
"); - builder.append("

You have provided the code " + userCode + ". Verify that this code matches what is shown on your device.

"); - builder.append("
"); - builder.append("
"); - } - builder.append("
"); - builder.append("
"); - builder.append("

The following permissions are requested by the above app.
Please review these and consent if you approve.

"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append(" "); - builder.append(" "); - if (userCode != null) { - builder.append(" "); - } - - for (String scope : scopesToAuthorize) { - builder.append("
"); - builder.append(" "); - builder.append(" "); - builder.append("
"); - } - - if (!scopesPreviouslyAuthorized.isEmpty()) { - builder.append("

You have already granted the following permissions to the above app:

"); - for (String scope : scopesPreviouslyAuthorized) { - builder.append("
"); - builder.append(" "); - builder.append(" "); - builder.append("
"); - } - } - - builder.append("
"); - builder.append(" "); - builder.append("
"); - builder.append("
"); - builder.append(" "); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append("

Your consent to provide access is required.
If you do not approve, click Cancel, in which case no information will be shared with the app.

"); - builder.append("
"); - builder.append("
"); - builder.append("
"); - builder.append(""); - builder.append(""); - // @formatter:on - - return builder.toString(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java deleted file mode 100644 index a2c687afe..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.GsonHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.util.ClassUtils; - -/** - * Utility methods for {@link HttpMessageConverter}'s. - * - * @author Joe Grandja - * @since 1.5 - */ -final class HttpMessageConverters { - - private static final boolean jackson2Present; - - private static final boolean gsonPresent; - - private static final boolean jsonbPresent; - - static { - ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); - jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) - && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); - gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); - jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); - } - - private HttpMessageConverters() { - } - - static GenericHttpMessageConverter getJsonMessageConverter() { - if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); - } - if (gsonPresent) { - return new GsonHttpMessageConverter(); - } - if (jsonbPresent) { - return new JsonbHttpMessageConverter(); - } - return null; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java deleted file mode 100644 index cd2df12cc..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.io.Writer; - -import com.nimbusds.jose.jwk.JWKMatcher; -import com.nimbusds.jose.jwk.JWKSelector; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} that processes JWK Set requests. - * - * @author Joe Grandja - * @since 0.0.1 - * @see com.nimbusds.jose.jwk.source.JWKSource - * @see JSON Web Key - * (JWK) - * @see Section 5 - * JWK Set Format - */ -public final class NimbusJwkSetEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for JWK Set requests. - */ - private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; - - private final JWKSource jwkSource; - - private final JWKSelector jwkSelector; - - private final RequestMatcher requestMatcher; - - /** - * Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters. - * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource} - */ - public NimbusJwkSetEndpointFilter(JWKSource jwkSource) { - this(jwkSource, DEFAULT_JWK_SET_ENDPOINT_URI); - } - - /** - * Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters. - * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource} - * @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests - */ - public NimbusJwkSetEndpointFilter(JWKSource jwkSource, String jwkSetEndpointUri) { - Assert.notNull(jwkSource, "jwkSource cannot be null"); - Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty"); - this.jwkSource = jwkSource; - this.jwkSelector = new JWKSelector(new JWKMatcher.Builder().build()); - this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, jwkSetEndpointUri); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.requestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - JWKSet jwkSet; - try { - jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null)); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to select the JWK(s) -> " + ex.getMessage(), ex); - } - - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - try (Writer writer = response.getWriter()) { - writer.write(jwkSet.toString()); // toString() excludes private keys - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java deleted file mode 100644 index d89a6f108..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.RedirectUrlBuilder; -import org.springframework.security.web.util.UrlUtils; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.NegatedRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -/** - * A {@code Filter} for the OAuth 2.0 Authorization Code Grant, which handles the - * processing of the OAuth 2.0 Authorization Request and Consent. - * - * @author Joe Grandja - * @author Paurav Munshi - * @author Daniel Garnier-Moiroux - * @author Anoop Garlapati - * @author Dmitriy Dubson - * @since 0.0.1 - * @see AuthenticationManager - * @see OAuth2AuthorizationCodeRequestAuthenticationProvider - * @see OAuth2AuthorizationConsentAuthenticationProvider - * @see Section 4.1 Authorization - * Code Grant - * @see Section 4.1.1 - * Authorization Request - * @see Section 4.1.2 - * Authorization Response - */ -public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for authorization requests. - */ - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher authorizationEndpointMatcher; - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendAuthorizationResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - - private SessionAuthenticationStrategy sessionAuthenticationStrategy = (authentication, request, response) -> { - }; - - private String consentPage; - - /** - * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2AuthorizationEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_AUTHORIZATION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param authorizationEndpointUri the endpoint {@code URI} for authorization requests - */ - public OAuth2AuthorizationEndpointFilter(AuthenticationManager authenticationManager, - String authorizationEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.authorizationEndpointMatcher = createDefaultRequestMatcher(authorizationEndpointUri); - // @formatter:off - this.authenticationConverter = new DelegatingAuthenticationConverter( - Arrays.asList( - new OAuth2AuthorizationCodeRequestAuthenticationConverter(), - new OAuth2AuthorizationConsentAuthenticationConverter())); - // @formatter:on - } - - private static RequestMatcher createDefaultRequestMatcher(String authorizationEndpointUri) { - RequestMatcher authorizationRequestGetMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, authorizationEndpointUri); - RequestMatcher authorizationRequestPostMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, authorizationEndpointUri); - - RequestMatcher responseTypeParameterMatcher = ( - request) -> request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null; - - RequestMatcher authorizationRequestMatcher = new OrRequestMatcher(authorizationRequestGetMatcher, - new AndRequestMatcher(authorizationRequestPostMatcher, responseTypeParameterMatcher)); - RequestMatcher authorizationConsentMatcher = new AndRequestMatcher(authorizationRequestPostMatcher, - new NegatedRequestMatcher(responseTypeParameterMatcher)); - - return new OrRequestMatcher(authorizationRequestMatcher, authorizationConsentMatcher); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.authorizationEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication authentication = this.authenticationConverter.convert(request); - if (authentication instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - Authentication authenticationResult = this.authenticationManager.authenticate(authentication); - - if (!authenticationResult.isAuthenticated()) { - // If the Principal (Resource Owner) is not authenticated then pass - // through the chain - // with the expectation that the authentication process will commence via - // AuthenticationEntryPoint - filterChain.doFilter(request, response); - return; - } - - if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authorization consent is required"); - } - sendAuthorizationConsent(request, response, - (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication, - authorizationConsentAuthenticationToken); - return; - } - - this.sessionAuthenticationStrategy.onAuthentication(authenticationResult, request, response); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult); - - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Authorization request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - * @since 0.3.1 - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract an - * Authorization Request (or Consent) from {@link HttpServletRequest} to an instance - * of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} or - * {@link OAuth2AuthorizationConsentAuthenticationToken} used for authenticating the - * request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract an Authorization Request (or Consent) from - * {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and returning the - * {@link OAuth2AuthorizationResponse Authorization Response}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationException} and returning the - * {@link OAuth2Error Error Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - /** - * Sets the {@link SessionAuthenticationStrategy} used for handling an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} before calling the - * {@link AuthenticationSuccessHandler}. If OpenID Connect is enabled, the default - * implementation tracks OpenID Connect sessions using a {@link SessionRegistry}. - * @param sessionAuthenticationStrategy the {@link SessionAuthenticationStrategy} used - * for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} - * @since 1.1 - */ - public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) { - Assert.notNull(sessionAuthenticationStrategy, "sessionAuthenticationStrategy cannot be null"); - this.sessionAuthenticationStrategy = sessionAuthenticationStrategy; - } - - /** - * Specify the URI to redirect Resource Owners to if consent is required. A default - * consent page will be generated when this attribute is not specified. - * @param consentPage the URI of the custom consent page to redirect to if consent is - * required (e.g. "/oauth2/consent") - */ - public void setConsentPage(String consentPage) { - this.consentPage = consentPage; - } - - private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response, - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication) throws IOException { - - String clientId = authorizationConsentAuthentication.getClientId(); - Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal(); - Set requestedScopes = authorizationCodeRequestAuthentication.getScopes(); - Set authorizedScopes = authorizationConsentAuthentication.getScopes(); - String state = authorizationConsentAuthentication.getState(); - - if (hasConsentUri()) { - String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request)) - .queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes)) - .queryParam(OAuth2ParameterNames.CLIENT_ID, clientId) - .queryParam(OAuth2ParameterNames.STATE, state) - .toUriString(); - this.redirectStrategy.sendRedirect(request, response, redirectUri); - } - else { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Displaying generated consent screen"); - } - DefaultConsentPage.displayConsent(request, response, clientId, principal, requestedScopes, authorizedScopes, - state, Collections.emptyMap()); - } - } - - private boolean hasConsentUri() { - return StringUtils.hasText(this.consentPage); - } - - private String resolveConsentUri(HttpServletRequest request) { - if (UrlUtils.isAbsoluteUrl(this.consentPage)) { - return this.consentPage; - } - RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme(request.getScheme()); - urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(request.getServerPort()); - urlBuilder.setContextPath(request.getContextPath()); - urlBuilder.setPathInfo(this.consentPage); - return urlBuilder.getUrl(); - } - - private void sendAuthorizationResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication; - UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(authorizationCodeRequestAuthentication.getRedirectUri()) - .queryParam(OAuth2ParameterNames.CODE, - authorizationCodeRequestAuthentication.getAuthorizationCode().getTokenValue()); - if (StringUtils.hasText(authorizationCodeRequestAuthentication.getState())) { - uriBuilder.queryParam(OAuth2ParameterNames.STATE, - UriUtils.encode(authorizationCodeRequestAuthentication.getState(), StandardCharsets.UTF_8)); - } - // build(true) -> Components are explicitly encoded - String redirectUri = uriBuilder.build(true).toUriString(); - this.redirectStrategy.sendRedirect(request, response, redirectUri); - } - - private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException { - - OAuth2AuthorizationCodeRequestAuthenticationException authorizationCodeRequestAuthenticationException = (OAuth2AuthorizationCodeRequestAuthenticationException) exception; - OAuth2Error error = authorizationCodeRequestAuthenticationException.getError(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authorizationCodeRequestAuthenticationException - .getAuthorizationCodeRequestAuthentication(); - - if (authorizationCodeRequestAuthentication == null - || !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { - response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString()); - return; - } - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Redirecting to client with error"); - } - - UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(authorizationCodeRequestAuthentication.getRedirectUri()) - .queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode()); - if (StringUtils.hasText(error.getDescription())) { - uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, - UriUtils.encode(error.getDescription(), StandardCharsets.UTF_8)); - } - if (StringUtils.hasText(error.getUri())) { - uriBuilder.queryParam(OAuth2ParameterNames.ERROR_URI, - UriUtils.encode(error.getUri(), StandardCharsets.UTF_8)); - } - if (StringUtils.hasText(authorizationCodeRequestAuthentication.getState())) { - uriBuilder.queryParam(OAuth2ParameterNames.STATE, - UriUtils.encode(authorizationCodeRequestAuthentication.getState(), StandardCharsets.UTF_8)); - } - // build(true) -> Components are explicitly encoded - String redirectUri = uriBuilder.build(true).toUriString(); - this.redirectStrategy.sendRedirect(request, response, redirectUri); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java deleted file mode 100644 index 82919ee64..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2AuthorizationServerMetadataHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - * @since 0.1.1 - * @see OAuth2AuthorizationServerMetadata - * @see AuthorizationServerSettings - * @see 3. - * Obtaining Authorization Server Metadata - */ -public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for OAuth 2.0 Authorization Server Metadata - * requests. - */ - private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; - - private final RequestMatcher requestMatcher = createRequestMatcher(); - - private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter(); - - private Consumer authorizationServerMetadataCustomizer = ( - authorizationServerMetadata) -> { - }; - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AuthorizationServerMetadata.Builder} allowing the ability to customize - * the claims of the Authorization Server's configuration. - * @param authorizationServerMetadataCustomizer the {@code Consumer} providing access - * to the {@link OAuth2AuthorizationServerMetadata.Builder} - * @since 0.4.0 - */ - public void setAuthorizationServerMetadataCustomizer( - Consumer authorizationServerMetadataCustomizer) { - Assert.notNull(authorizationServerMetadataCustomizer, "authorizationServerMetadataCustomizer cannot be null"); - this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.requestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String issuer = authorizationServerContext.getIssuer(); - AuthorizationServerSettings authorizationServerSettings = authorizationServerContext - .getAuthorizationServerSettings(); - - OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata - .builder() - .issuer(issuer) - .authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint())) - .pushedAuthorizationRequestEndpoint( - asUrl(issuer, authorizationServerSettings.getPushedAuthorizationRequestEndpoint())) - .deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint())) - .tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint())) - .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) - .jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint())) - .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue()) - .grantType(AuthorizationGrantType.DEVICE_CODE.getValue()) - .grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()) - .tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint())) - .tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods()) - .tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint())) - .tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods()) - .codeChallengeMethod("S256") - .tlsClientCertificateBoundAccessTokens(true) - .dPoPSigningAlgorithms(dPoPSigningAlgorithms()); - - this.authorizationServerMetadataCustomizer.accept(authorizationServerMetadata); - - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.authorizationServerMetadataHttpMessageConverter.write(authorizationServerMetadata.build(), - MediaType.APPLICATION_JSON, httpResponse); - } - - private static RequestMatcher createRequestMatcher() { - final RequestMatcher defaultRequestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI); - final RequestMatcher multipleIssuersRequestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI + "/**"); - return (request) -> AuthorizationServerContextHolder.getContext() - .getAuthorizationServerSettings() - .isMultipleIssuersAllowed() ? multipleIssuersRequestMatcher.matches(request) - : defaultRequestMatcher.matches(request); - } - - private static Consumer> clientAuthenticationMethods() { - return (authenticationMethods) -> { - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.TLS_CLIENT_AUTH.getValue()); - authenticationMethods.add(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.getValue()); - }; - } - - private static Consumer> dPoPSigningAlgorithms() { - return (algs) -> { - algs.add(JwsAlgorithms.RS256); - algs.add(JwsAlgorithms.RS384); - algs.add(JwsAlgorithms.RS512); - algs.add(JwsAlgorithms.PS256); - algs.add(JwsAlgorithms.PS384); - algs.add(JwsAlgorithms.PS512); - algs.add(JwsAlgorithms.ES256); - algs.add(JwsAlgorithms.ES384); - algs.add(JwsAlgorithms.ES512); - }; - } - - private static String asUrl(String issuer, String endpoint) { - return UriComponentsBuilder.fromUriString(issuer).path(endpoint).toUriString(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java deleted file mode 100644 index d620625b9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.util.Arrays; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.X509ClientCertificateAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} that processes an authentication request for an OAuth 2.0 Client. - * - * @author Joe Grandja - * @author Patryk Kostrzewa - * @since 0.0.1 - * @see AuthenticationManager - * @see JwtClientAssertionAuthenticationConverter - * @see JwtClientAssertionAuthenticationProvider - * @see X509ClientCertificateAuthenticationConverter - * @see X509ClientCertificateAuthenticationProvider - * @see ClientSecretBasicAuthenticationConverter - * @see ClientSecretPostAuthenticationConverter - * @see ClientSecretAuthenticationProvider - * @see PublicClientAuthenticationConverter - * @see PublicClientAuthenticationProvider - * @see Section 2.3 Client - * Authentication - * @see Section 3.2.1 Token - * Endpoint Client Authentication - */ -public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter { - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher requestMatcher; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess; - - private AuthenticationFailureHandler authenticationFailureHandler = this::onAuthenticationFailure; - - /** - * Constructs an {@code OAuth2ClientAuthenticationFilter} using the provided - * parameters. - * @param authenticationManager the {@link AuthenticationManager} used for - * authenticating the client - * @param requestMatcher the {@link RequestMatcher} used for matching against the - * {@code HttpServletRequest} - */ - public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager, - RequestMatcher requestMatcher) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.notNull(requestMatcher, "requestMatcher cannot be null"); - this.authenticationManager = authenticationManager; - this.requestMatcher = requestMatcher; - // @formatter:off - this.authenticationConverter = new DelegatingAuthenticationConverter( - Arrays.asList( - new JwtClientAssertionAuthenticationConverter(), - new ClientSecretBasicAuthenticationConverter(), - new ClientSecretPostAuthenticationConverter(), - new PublicClientAuthenticationConverter(), - new X509ClientCertificateAuthenticationConverter())); - // @formatter:on - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.requestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication authenticationRequest = this.authenticationConverter.convert(request); - if (authenticationRequest instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - if (authenticationRequest != null) { - validateClientIdentifier(authenticationRequest); - Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest); - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult); - } - filterChain.doFilter(request, response); - - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Client authentication failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract client - * credentials from {@link HttpServletRequest} to an instance of - * {@link OAuth2ClientAuthenticationToken} used for authenticating the client. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract client credentials from {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling a successful client - * authentication and associating the {@link OAuth2ClientAuthenticationToken} to the - * {@link SecurityContext}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling a successful client authentication - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling a failed client - * authentication and returning the {@link OAuth2Error Error Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling a failed client authentication - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); - } - - private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException { - - SecurityContextHolder.clearContext(); - - // TODO - // The authorization server MAY return an HTTP 401 (Unauthorized) status code - // to indicate which HTTP authentication schemes are supported. - // If the client attempted to authenticate via the "Authorization" request header - // field, - // the authorization server MUST respond with an HTTP 401 (Unauthorized) status - // code and - // include the "WWW-Authenticate" response header field - // matching the authentication scheme used by the client. - - OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) { - httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); - } - else { - httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); - } - // We don't want to reveal too much information to the caller so just return the - // error code - OAuth2Error errorResponse = new OAuth2Error(error.getErrorCode()); - this.errorHttpResponseConverter.write(errorResponse, null, httpResponse); - } - - private static void validateClientIdentifier(Authentication authentication) { - if (!(authentication instanceof OAuth2ClientAuthenticationToken)) { - return; - } - - // As per spec, in Appendix A.1. - // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#appendix-A.1 - // The syntax for client_id is *VSCHAR (%x20-7E): - // -> Hex 20 -> ASCII 32 -> space - // -> Hex 7E -> ASCII 126 -> tilde - - OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; - String clientId = (String) clientAuthentication.getPrincipal(); - for (int i = 0; i < clientId.length(); i++) { - char charAt = clientId.charAt(i); - if (!(charAt >= 32 && charAt <= 126)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java deleted file mode 100644 index cf3723b58..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.RedirectUrlBuilder; -import org.springframework.security.web.util.UrlUtils; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Filter} for the OAuth 2.0 Device Authorization endpoint, which handles the - * processing of the OAuth 2.0 Device Authorization Request. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationManager - * @see OAuth2DeviceAuthorizationRequestAuthenticationConverter - * @see OAuth2DeviceAuthorizationRequestAuthenticationProvider - * @see OAuth 2.0 - * Device Authorization Grant - * @see Section 3.1 Device - * Authorization Request - * @see Section 3.2 Device - * Authorization Response - */ -public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerRequestFilter { - - private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher deviceAuthorizationEndpointMatcher; - - private final HttpMessageConverter deviceAuthorizationHttpResponseConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendDeviceAuthorizationResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - private String verificationUri = OAuth2DeviceVerificationEndpointFilter.DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI; - - /** - * Constructs an {@code OAuth2DeviceAuthorizationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2DeviceAuthorizationEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2DeviceAuthorizationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param deviceAuthorizationEndpointUri the endpoint {@code URI} for device - * authorization requests - */ - public OAuth2DeviceAuthorizationEndpointFilter(AuthenticationManager authenticationManager, - String deviceAuthorizationEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(deviceAuthorizationEndpointUri, "deviceAuthorizationEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.deviceAuthorizationEndpointMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, deviceAuthorizationEndpointUri); - this.authenticationConverter = new OAuth2DeviceAuthorizationRequestAuthenticationConverter(); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.deviceAuthorizationEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication deviceAuthorizationRequestAuthentication = this.authenticationConverter.convert(request); - if (deviceAuthorizationRequestAuthentication instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - - Authentication deviceAuthorizationRequestAuthenticationResult = this.authenticationManager - .authenticate(deviceAuthorizationRequestAuthentication); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - deviceAuthorizationRequestAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - SecurityContextHolder.clearContext(); - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Device authorization request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Device - * Authorization Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} used for authenticating - * the request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract a Device Authorization Request from - * {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} and returning the - * {@link OAuth2DeviceAuthorizationResponse Device Authorization Response}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - /** - * Sets the end-user verification {@code URI} on the authorization server. - * @param verificationUri the end-user verification {@code URI} on the authorization - * server - * @see Section 3.2 Device - * Authorization Response - */ - public void setVerificationUri(String verificationUri) { - Assert.hasText(verificationUri, "verificationUri cannot be empty"); - this.verificationUri = verificationUri; - } - - private void sendDeviceAuthorizationResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthentication = (OAuth2DeviceAuthorizationRequestAuthenticationToken) authentication; - - OAuth2DeviceCode deviceCode = deviceAuthorizationRequestAuthentication.getDeviceCode(); - OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode(); - - // Generate the fully-qualified verification URI - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(resolveVerificationUri(request)); - String verificationUri = uriComponentsBuilder.build().toUriString(); - // @formatter:off - String verificationUriComplete = uriComponentsBuilder - .queryParam(OAuth2ParameterNames.USER_CODE, userCode.getTokenValue()) - .build().toUriString(); - // @formatter:on - - // @formatter:off - OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = - OAuth2DeviceAuthorizationResponse.with(deviceCode, userCode) - .verificationUri(verificationUri) - .verificationUriComplete(verificationUriComplete) - .build(); - // @formatter:on - - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.deviceAuthorizationHttpResponseConverter.write(deviceAuthorizationResponse, null, httpResponse); - } - - private String resolveVerificationUri(HttpServletRequest request) { - if (UrlUtils.isAbsoluteUrl(this.verificationUri)) { - return this.verificationUri; - } - RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme(request.getScheme()); - urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(request.getServerPort()); - urlBuilder.setContextPath(request.getContextPath()); - urlBuilder.setPathInfo(this.verificationUri); - return urlBuilder.getUrl(); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java deleted file mode 100644 index 8f91a6562..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.RedirectUrlBuilder; -import org.springframework.security.web.util.UrlUtils; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A {@code Filter} for the OAuth 2.0 Device Authorization Grant, which handles the - * processing of the Device Verification Request (submission of the user code) and the - * Device Authorization Consent. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationManager - * @see OAuth2DeviceVerificationAuthenticationConverter - * @see OAuth2DeviceVerificationAuthenticationProvider - * @see OAuth2DeviceAuthorizationConsentAuthenticationConverter - * @see OAuth2DeviceAuthorizationConsentAuthenticationProvider - * @see OAuth 2.0 - * Device Authorization Grant - * @see Section 3.3 User - * Interaction - */ -public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequestFilter { - - static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher deviceVerificationEndpointMatcher; - - private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = new SimpleUrlAuthenticationSuccessHandler( - "/?success"); - - private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - - private String consentPage; - - /** - * Constructs an {@code OAuth2DeviceVerificationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2DeviceVerificationEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2DeviceVerificationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param deviceVerificationEndpointUri the endpoint {@code URI} for device - * verification requests - */ - public OAuth2DeviceVerificationEndpointFilter(AuthenticationManager authenticationManager, - String deviceVerificationEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(deviceVerificationEndpointUri, "deviceVerificationEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.deviceVerificationEndpointMatcher = createDefaultRequestMatcher(deviceVerificationEndpointUri); - // @formatter:off - this.authenticationConverter = new DelegatingAuthenticationConverter( - Arrays.asList( - new OAuth2DeviceVerificationAuthenticationConverter(), - new OAuth2DeviceAuthorizationConsentAuthenticationConverter())); - // @formatter:on - } - - private RequestMatcher createDefaultRequestMatcher(String deviceVerificationEndpointUri) { - RequestMatcher verificationRequestGetMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.GET, deviceVerificationEndpointUri); - RequestMatcher verificationRequestPostMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, deviceVerificationEndpointUri); - RequestMatcher userCodeParameterMatcher = ( - request) -> request.getParameter(OAuth2ParameterNames.USER_CODE) != null; - - return new AndRequestMatcher( - new OrRequestMatcher(verificationRequestGetMatcher, verificationRequestPostMatcher), - userCodeParameterMatcher); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.deviceVerificationEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication authentication = this.authenticationConverter.convert(request); - if (authentication instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - - Authentication authenticationResult = this.authenticationManager.authenticate(authentication); - if (!authenticationResult.isAuthenticated()) { - // If the Principal (Resource Owner) is not authenticated then pass - // through the chain - // with the expectation that the authentication process will commence via - // AuthenticationEntryPoint - filterChain.doFilter(request, response); - return; - } - - if (authenticationResult instanceof OAuth2DeviceAuthorizationConsentAuthenticationToken) { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Device authorization consent is required"); - } - sendAuthorizationConsent(request, response, authenticationResult); - return; - } - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult); - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Device verification request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Device - * Verification Request (or Device Authorization Consent) from - * {@link HttpServletRequest} to an instance of - * {@link OAuth2DeviceVerificationAuthenticationToken} or - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} used for authenticating - * the request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract a Device Verification Request (or Device Authorization - * Consent) from {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2DeviceVerificationAuthenticationToken} and returning the response. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2DeviceVerificationAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - /** - * Specify the URI to redirect Resource Owners to if consent is required. A default - * consent page will be generated when this attribute is not specified. - * @param consentPage the URI of the custom consent page to redirect to if consent is - * required (e.g. "/oauth2/consent") - */ - public void setConsentPage(String consentPage) { - this.consentPage = consentPage; - } - - private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - OAuth2DeviceAuthorizationConsentAuthenticationToken authorizationConsentAuthentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) authentication; - - String clientId = authorizationConsentAuthentication.getClientId(); - Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal(); - Set requestedScopes = authorizationConsentAuthentication.getRequestedScopes(); - Set authorizedScopes = authorizationConsentAuthentication.getScopes(); - String state = authorizationConsentAuthentication.getState(); - String userCode = authorizationConsentAuthentication.getUserCode(); - - if (hasConsentUri()) { - String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request)) - .queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes)) - .queryParam(OAuth2ParameterNames.CLIENT_ID, clientId) - .queryParam(OAuth2ParameterNames.STATE, state) - .queryParam(OAuth2ParameterNames.USER_CODE, userCode) - .toUriString(); - this.redirectStrategy.sendRedirect(request, response, redirectUri); - } - else { - if (this.logger.isTraceEnabled()) { - this.logger.trace("Displaying generated consent screen"); - } - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.USER_CODE, userCode); - DefaultConsentPage.displayConsent(request, response, clientId, principal, requestedScopes, authorizedScopes, - state, additionalParameters); - } - } - - private boolean hasConsentUri() { - return StringUtils.hasText(this.consentPage); - } - - private String resolveConsentUri(HttpServletRequest request) { - if (UrlUtils.isAbsoluteUrl(this.consentPage)) { - return this.consentPage; - } - RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); - urlBuilder.setScheme(request.getScheme()); - urlBuilder.setServerName(request.getServerName()); - urlBuilder.setPort(request.getServerPort()); - urlBuilder.setContextPath(request.getContextPath()); - urlBuilder.setPathInfo(this.consentPage); - return urlBuilder.getUrl(); - } - - private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authenticationException) throws IOException { - - OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError(); - response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString()); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java deleted file mode 100644 index d50d5cdd2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.LinkedHashMap; -import java.util.Map; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} for the OAuth 2.0 Pushed Authorization Request endpoint, which handles - * the processing of the OAuth 2.0 Pushed Authorization Request. - * - * @author Joe Grandja - * @since 1.5 - * @see AuthenticationManager - * @see OAuth2PushedAuthorizationRequestAuthenticationProvider - * @see Section - * 2. Pushed Authorization Request Endpoint - * @see Section 2.1 Pushed - * Authorization Request - * @see Section 2.2 Pushed - * Authorization Response - */ -public final class OAuth2PushedAuthorizationRequestEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for pushed authorization requests. - */ - private static final String DEFAULT_PUSHED_AUTHORIZATION_REQUEST_ENDPOINT_URI = "/oauth2/par"; - - private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - - private static final GenericHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters - .getJsonMessageConverter(); - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher pushedAuthorizationRequestEndpointMatcher; - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendPushedAuthorizationResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - /** - * Constructs an {@code OAuth2PushedAuthorizationRequestEndpointFilter} using the - * provided parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2PushedAuthorizationRequestEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_PUSHED_AUTHORIZATION_REQUEST_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2PushedAuthorizationRequestEndpointFilter} using the - * provided parameters. - * @param authenticationManager the authentication manager - * @param pushedAuthorizationRequestEndpointUri the endpoint {@code URI} for pushed - * authorization requests - */ - public OAuth2PushedAuthorizationRequestEndpointFilter(AuthenticationManager authenticationManager, - String pushedAuthorizationRequestEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(pushedAuthorizationRequestEndpointUri, "pushedAuthorizationRequestEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.pushedAuthorizationRequestEndpointMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, pushedAuthorizationRequestEndpointUri); - this.authenticationConverter = new OAuth2AuthorizationCodeRequestAuthenticationConverter(); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.pushedAuthorizationRequestEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication pushedAuthorizationRequestAuthentication = this.authenticationConverter.convert(request); - if (pushedAuthorizationRequestAuthentication instanceof AbstractAuthenticationToken) { - ((AbstractAuthenticationToken) pushedAuthorizationRequestAuthentication) - .setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - Authentication pushedAuthorizationRequestAuthenticationResult = this.authenticationManager - .authenticate(pushedAuthorizationRequestAuthentication); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - pushedAuthorizationRequestAuthenticationResult); - - } - catch (OAuth2AuthenticationException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Pushed authorization request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Pushed - * Authorization Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken} used for authenticating - * the request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract a Pushed Authorization Request from - * {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken} and returning the - * Pushed Authorization Response. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2PushedAuthorizationRequestAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void sendPushedAuthorizationResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthentication = (OAuth2PushedAuthorizationRequestAuthenticationToken) authentication; - - Map pushedAuthorizationResponse = new LinkedHashMap<>(); - pushedAuthorizationResponse.put(OAuth2ParameterNames.REQUEST_URI, - pushedAuthorizationRequestAuthentication.getRequestUri()); - long expiresIn = ChronoUnit.SECONDS.between(Instant.now(), - pushedAuthorizationRequestAuthentication.getRequestUriExpiresAt()); - pushedAuthorizationResponse.put(OAuth2ParameterNames.EXPIRES_IN, expiresIn); - - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(HttpStatus.CREATED); - - JSON_MESSAGE_CONVERTER.write(pushedAuthorizationResponse, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java deleted file mode 100644 index 5a94e682a..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.util.Arrays; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AccessTokenResponseAuthenticationSuccessHandler; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenExchangeAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} for the OAuth 2.0 Token endpoint, which handles the processing of an - * OAuth 2.0 Authorization Grant. - * - *

- * It converts the OAuth 2.0 Authorization Grant request to an {@link Authentication}, - * which is then authenticated by the {@link AuthenticationManager}. If the authentication - * succeeds, the {@link AuthenticationManager} returns an - * {@link OAuth2AccessTokenAuthenticationToken}, which is returned in the OAuth 2.0 Access - * Token response. In case of any error, an {@link OAuth2Error} is returned in the OAuth - * 2.0 Error response. - * - *

- * By default, this {@code Filter} responds to authorization grant requests at the - * {@code URI} {@code /oauth2/token} and {@code HttpMethod} {@code POST}. - * - *

- * The default endpoint {@code URI} {@code /oauth2/token} may be overridden via the - * constructor {@link #OAuth2TokenEndpointFilter(AuthenticationManager, String)}. - * - * @author Joe Grandja - * @author Madhu Bhat - * @author Daniel Garnier-Moiroux - * @author Dmitriy Dubson - * @since 0.0.1 - * @see AuthenticationManager - * @see OAuth2AuthorizationCodeAuthenticationProvider - * @see OAuth2RefreshTokenAuthenticationProvider - * @see OAuth2ClientCredentialsAuthenticationProvider - * @see OAuth2DeviceCodeAuthenticationProvider - * @see Section - * 3.2 Token Endpoint - */ -public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for access token requests. - */ - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher tokenEndpointMatcher; - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2AccessTokenResponseAuthenticationSuccessHandler(); - - private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - /** - * Constructs an {@code OAuth2TokenEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_TOKEN_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2TokenEndpointFilter} using the provided parameters. - * @param authenticationManager the authentication manager - * @param tokenEndpointUri the endpoint {@code URI} for access token requests - */ - public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager, String tokenEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(tokenEndpointUri, "tokenEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.tokenEndpointMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, tokenEndpointUri); - // @formatter:off - this.authenticationConverter = new DelegatingAuthenticationConverter( - Arrays.asList( - new OAuth2AuthorizationCodeAuthenticationConverter(), - new OAuth2RefreshTokenAuthenticationConverter(), - new OAuth2ClientCredentialsAuthenticationConverter(), - new OAuth2DeviceCodeAuthenticationConverter(), - new OAuth2TokenExchangeAuthenticationConverter()) - ); - // @formatter:on - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.tokenEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE); - if (grantTypes == null || grantTypes.length != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE); - } - - Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request); - if (authorizationGrantAuthentication == null) { - throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE); - } - if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationManager - .authenticate(authorizationGrantAuthentication); - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication); - } - catch (OAuth2AuthenticationException ex) { - SecurityContextHolder.clearContext(); - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Token request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract an Access - * Token Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the - * authorization grant. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract an Access Token Request from {@link HttpServletRequest} - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2AccessTokenAuthenticationToken} and returning the - * {@link OAuth2AccessTokenResponse Access Token Response}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2AccessTokenAuthenticationToken} - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java deleted file mode 100644 index 297896705..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} for the OAuth 2.0 Token Introspection endpoint. - * - * @author Gerardo Roza - * @author Joe Grandja - * @author Gaurav Tiwari - * @since 0.1.1 - * @see OAuth2TokenIntrospectionAuthenticationProvider - * @see Section 2 - * Introspection Endpoint - * @see Section - * 2.1 Introspection Request - */ -public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for token introspection requests. - */ - private static final String DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI = "/oauth2/introspect"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher tokenIntrospectionEndpointMatcher; - - private AuthenticationConverter authenticationConverter; - - private final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendIntrospectionResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - /** - * Constructs an {@code OAuth2TokenIntrospectionEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2TokenIntrospectionEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2TokenIntrospectionEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param tokenIntrospectionEndpointUri the endpoint {@code URI} for token - * introspection requests - */ - public OAuth2TokenIntrospectionEndpointFilter(AuthenticationManager authenticationManager, - String tokenIntrospectionEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(tokenIntrospectionEndpointUri, "tokenIntrospectionEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.tokenIntrospectionEndpointMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, tokenIntrospectionEndpointUri); - this.authenticationConverter = new OAuth2TokenIntrospectionAuthenticationConverter(); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.tokenIntrospectionEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication tokenIntrospectionAuthentication = this.authenticationConverter.convert(request); - Authentication tokenIntrospectionAuthenticationResult = this.authenticationManager - .authenticate(tokenIntrospectionAuthentication); - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - tokenIntrospectionAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - SecurityContextHolder.clearContext(); - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Token introspection request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract an - * Introspection Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the - * request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract an Introspection Request from {@link HttpServletRequest} - * @since 0.2.3 - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2TokenIntrospectionAuthenticationToken}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2TokenIntrospectionAuthenticationToken} - * @since 0.2.3 - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Resonse}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - * @since 0.2.3 - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void sendIntrospectionResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException { - - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = (OAuth2TokenIntrospectionAuthenticationToken) authentication; - OAuth2TokenIntrospection tokenClaims = tokenIntrospectionAuthentication.getTokenClaims(); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.tokenIntrospectionHttpResponseConverter.write(tokenClaims, null, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java deleted file mode 100644 index 8da90f71c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.web.filter.OncePerRequestFilter; - -/** - * A {@code Filter} for the OAuth 2.0 Token Revocation endpoint. - * - * @author Vivek Babu - * @author Joe Grandja - * @author Arfat Chaus - * @since 0.0.3 - * @see OAuth2TokenRevocationAuthenticationProvider - * @see Section 2 - * Token Revocation - * @see Section - * 2.1 Revocation Request - */ -public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFilter { - - /** - * The default endpoint {@code URI} for token revocation requests. - */ - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private final AuthenticationManager authenticationManager; - - private final RequestMatcher tokenRevocationEndpointMatcher; - - private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - - private AuthenticationConverter authenticationConverter; - - private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendRevocationSuccessResponse; - - private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - /** - * Constructs an {@code OAuth2TokenRevocationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - */ - public OAuth2TokenRevocationEndpointFilter(AuthenticationManager authenticationManager) { - this(authenticationManager, DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI); - } - - /** - * Constructs an {@code OAuth2TokenRevocationEndpointFilter} using the provided - * parameters. - * @param authenticationManager the authentication manager - * @param tokenRevocationEndpointUri the endpoint {@code URI} for token revocation - * requests - */ - public OAuth2TokenRevocationEndpointFilter(AuthenticationManager authenticationManager, - String tokenRevocationEndpointUri) { - Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - Assert.hasText(tokenRevocationEndpointUri, "tokenRevocationEndpointUri cannot be empty"); - this.authenticationManager = authenticationManager; - this.tokenRevocationEndpointMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, tokenRevocationEndpointUri); - this.authenticationConverter = new OAuth2TokenRevocationAuthenticationConverter(); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - if (!this.tokenRevocationEndpointMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - try { - Authentication tokenRevocationAuthentication = this.authenticationConverter.convert(request); - if (tokenRevocationAuthentication instanceof AbstractAuthenticationToken authenticationToken) { - authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - } - - Authentication tokenRevocationAuthenticationResult = this.authenticationManager - .authenticate(tokenRevocationAuthentication); - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - tokenRevocationAuthenticationResult); - } - catch (OAuth2AuthenticationException ex) { - SecurityContextHolder.clearContext(); - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Token revocation request failed: %s", ex.getError()), ex); - } - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - } - - /** - * Sets the {@link AuthenticationDetailsSource} used for building an authentication - * details instance from {@link HttpServletRequest}. - * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for - * building an authentication details instance from {@link HttpServletRequest} - * @since 1.4 - */ - public void setAuthenticationDetailsSource( - AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * Sets the {@link AuthenticationConverter} used when attempting to extract a Revoke - * Token Request from {@link HttpServletRequest} to an instance of - * {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the - * request. - * @param authenticationConverter the {@link AuthenticationConverter} used when - * attempting to extract a Revoke Token Request from {@link HttpServletRequest} - * @since 0.2.2 - */ - public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { - Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); - this.authenticationConverter = authenticationConverter; - } - - /** - * Sets the {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2TokenRevocationAuthenticationToken}. - * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used - * for handling an {@link OAuth2TokenRevocationAuthenticationToken} - * @since 0.2.2 - */ - public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { - Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null"); - this.authenticationSuccessHandler = authenticationSuccessHandler; - } - - /** - * Sets the {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error - * Response}. - * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used - * for handling an {@link OAuth2AuthenticationException} - * @since 0.2.2 - */ - public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { - Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null"); - this.authenticationFailureHandler = authenticationFailureHandler; - } - - private void sendRevocationSuccessResponse(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - response.setStatus(HttpStatus.OK.value()); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java deleted file mode 100644 index 794a97761..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.web.authentication; - -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpHeaders; -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract HTTP Basic credentials from {@link HttpServletRequest} and then - * converts to an {@link OAuth2ClientAuthenticationToken} used for authenticating the - * client. - * - * @author Patryk Kostrzewa - * @author Joe Grandja - * @since 0.0.1 - * @see AuthenticationConverter - * @see OAuth2ClientAuthenticationToken - * @see OAuth2ClientAuthenticationFilter - */ -public final class ClientSecretBasicAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - String header = request.getHeader(HttpHeaders.AUTHORIZATION); - if (header == null) { - return null; - } - - String[] parts = header.split("\\s"); - if (!parts[0].equalsIgnoreCase("Basic")) { - return null; - } - - if (parts.length != 2) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - byte[] decodedCredentials; - try { - decodedCredentials = Base64.getDecoder().decode(parts[1].getBytes(StandardCharsets.UTF_8)); - } - catch (IllegalArgumentException ex) { - throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex); - } - - String credentialsString = new String(decodedCredentials, StandardCharsets.UTF_8); - String[] credentials = credentialsString.split(":", 2); - if (credentials.length != 2 || !StringUtils.hasText(credentials[0]) || !StringUtils.hasText(credentials[1])) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - String clientID; - String clientSecret; - try { - clientID = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8.name()); - clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8.name()); - } - catch (Exception ex) { - throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex); - } - - return new OAuth2ClientAuthenticationToken(clientID, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - clientSecret, OAuth2EndpointUtils.getParametersIfMatchesAuthorizationCodeGrantRequest(request)); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java deleted file mode 100644 index 80c8bac84..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract client credentials from POST parameters of - * {@link HttpServletRequest} and then converts to an - * {@link OAuth2ClientAuthenticationToken} used for authenticating the client. - * - * @author Anoop Garlapati - * @since 0.1.0 - * @see AuthenticationConverter - * @see OAuth2ClientAuthenticationToken - * @see OAuth2ClientAuthenticationFilter - * @see Section 2.3.1 Client Password - */ -public final class ClientSecretPostAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId)) { - return null; - } - - if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - // client_secret (REQUIRED) - String clientSecret = parameters.getFirst(OAuth2ParameterNames.CLIENT_SECRET); - if (!StringUtils.hasText(clientSecret)) { - return null; - } - - if (parameters.get(OAuth2ParameterNames.CLIENT_SECRET).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - Map additionalParameters = OAuth2EndpointUtils - .getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ID, - OAuth2ParameterNames.CLIENT_SECRET); - - return new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.CLIENT_SECRET_POST, - clientSecret, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java deleted file mode 100644 index 7a09625fa..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a JWT client assertion credential from {@link HttpServletRequest} - * and then converts to an {@link OAuth2ClientAuthenticationToken} used for authenticating - * the client. - * - * @author Rafal Lewczuk - * @since 0.2.2 - * @see AuthenticationConverter - * @see OAuth2ClientAuthenticationToken - * @see OAuth2ClientAuthenticationFilter - */ -public final class JwtClientAssertionAuthenticationConverter implements AuthenticationConverter { - - private static final ClientAuthenticationMethod JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD = new ClientAuthenticationMethod( - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - if (parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE) == null - || parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION) == null) { - return null; - } - - // client_assertion_type (REQUIRED) - String clientAssertionType = parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); - if (parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - if (!JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD.getValue().equals(clientAssertionType)) { - return null; - } - - // client_assertion (REQUIRED) - String jwtAssertion = parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION); - if (parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - // client_id (OPTIONAL as per specification but REQUIRED by this implementation) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - Map additionalParameters = OAuth2EndpointUtils - .getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, - OAuth2ParameterNames.CLIENT_ASSERTION, OAuth2ParameterNames.CLIENT_ID); - - return new OAuth2ClientAuthenticationToken(clientId, JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion, - additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java deleted file mode 100644 index 918be8fe5..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.io.IOException; -import java.time.temporal.ChronoUnit; -import java.util.Map; -import java.util.function.Consumer; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -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.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * An implementation of an {@link AuthenticationSuccessHandler} used for handling an - * {@link OAuth2AccessTokenAuthenticationToken} and returning the - * {@link OAuth2AccessTokenResponse Access Token Response}. - * - * @author Dmitriy Dubson - * @since 1.3 - * @see AuthenticationSuccessHandler - * @see OAuth2AccessTokenResponseHttpMessageConverter - */ -public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - - private final Log logger = LogFactory.getLog(getClass()); - - private final HttpMessageConverter accessTokenResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private Consumer accessTokenResponseCustomizer; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - if (!(authentication instanceof OAuth2AccessTokenAuthenticationToken accessTokenAuthentication)) { - if (this.logger.isErrorEnabled()) { - this.logger.error(Authentication.class.getSimpleName() + " must be of type " - + OAuth2AccessTokenAuthenticationToken.class.getName() + " but was " - + authentication.getClass().getName()); - } - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, - "Unable to process the access token response.", null); - throw new OAuth2AuthenticationException(error); - } - - OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken(); - OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken(); - Map additionalParameters = accessTokenAuthentication.getAdditionalParameters(); - - OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) - .tokenType(accessToken.getTokenType()) - .scopes(accessToken.getScopes()); - if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) { - builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt())); - } - if (refreshToken != null) { - builder.refreshToken(refreshToken.getTokenValue()); - } - if (!CollectionUtils.isEmpty(additionalParameters)) { - builder.additionalParameters(additionalParameters); - } - - if (this.accessTokenResponseCustomizer != null) { - // @formatter:off - OAuth2AccessTokenAuthenticationContext accessTokenAuthenticationContext = - OAuth2AccessTokenAuthenticationContext.with(accessTokenAuthentication) - .accessTokenResponse(builder) - .build(); - // @formatter:on - this.accessTokenResponseCustomizer.accept(accessTokenAuthenticationContext); - if (this.logger.isTraceEnabled()) { - this.logger.trace("Customized access token response"); - } - } - - OAuth2AccessTokenResponse accessTokenResponse = builder.build(); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.accessTokenResponseConverter.write(accessTokenResponse, null, httpResponse); - } - - /** - * Sets the {@code Consumer} providing access to the - * {@link OAuth2AccessTokenAuthenticationContext} containing an - * {@link OAuth2AccessTokenResponse.Builder} and additional context information. - * @param accessTokenResponseCustomizer the {@code Consumer} providing access to the - * {@link OAuth2AccessTokenAuthenticationContext} containing an - * {@link OAuth2AccessTokenResponse.Builder} - */ - public void setAccessTokenResponseCustomizer( - Consumer accessTokenResponseCustomizer) { - Assert.notNull(accessTokenResponseCustomizer, "accessTokenResponseCustomizer cannot be null"); - this.accessTokenResponseCustomizer = accessTokenResponseCustomizer; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java deleted file mode 100644 index fafdaa4e9..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Access Token Request from {@link HttpServletRequest} for the - * OAuth 2.0 Authorization Code Grant and then converts it to an - * {@link OAuth2AuthorizationCodeAuthenticationToken} used for authenticating the - * authorization grant. - * - * @author Joe Grandja - * @since 0.1.2 - * @see AuthenticationConverter - * @see OAuth2AuthorizationCodeAuthenticationToken - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2AuthorizationCodeAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // grant_type (REQUIRED) - String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(grantType)) { - return null; - } - - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - // code (REQUIRED) - String code = parameters.getFirst(OAuth2ParameterNames.CODE); - if (!StringUtils.hasText(code) || parameters.get(OAuth2ParameterNames.CODE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CODE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - // redirect_uri (REQUIRED) - // Required only if the "redirect_uri" parameter was included in the authorization - // request - String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.CLIENT_ID) - && !key.equals(OAuth2ParameterNames.CODE) && !key.equals(OAuth2ParameterNames.REDIRECT_URI)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - // Validate DPoP Proof HTTP Header (if available) - OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); - - return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java deleted file mode 100644 index 5afc25ca5..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.OAuth2PushedAuthorizationRequestEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Authorization Request from {@link HttpServletRequest} for the - * OAuth 2.0 Authorization Code Grant and then converts it to an - * {@link OAuth2AuthorizationCodeRequestAuthenticationToken} OR - * {@link OAuth2PushedAuthorizationRequestAuthenticationToken} used for authenticating the - * request. - * - * @author Joe Grandja - * @since 0.1.2 - * @see AuthenticationConverter - * @see OAuth2AuthorizationCodeRequestAuthenticationToken - * @see OAuth2PushedAuthorizationRequestAuthenticationToken - * @see OAuth2AuthorizationEndpointFilter - * @see OAuth2PushedAuthorizationRequestEndpointFilter - */ -public final class OAuth2AuthorizationCodeRequestAuthenticationConverter implements AuthenticationConverter { - - private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; - - private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1"; - - private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", - "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - private final RequestMatcher requestMatcher = createDefaultRequestMatcher(); - - @Override - public Authentication convert(HttpServletRequest request) { - if (!this.requestMatcher.matches(request)) { - return null; - } - - MultiValueMap parameters = "GET".equals(request.getMethod()) - ? OAuth2EndpointUtils.getQueryParameters(request) : OAuth2EndpointUtils.getFormParameters(request); - - boolean pushedAuthorizationRequest = isPushedAuthorizationRequest(request); - - // request_uri (OPTIONAL) - provided if an authorization request was previously - // pushed (RFC 9126 OAuth 2.0 Pushed Authorization Requests) - String requestUri = parameters.getFirst(OAuth2ParameterNames.REQUEST_URI); - if (StringUtils.hasText(requestUri)) { - if (pushedAuthorizationRequest) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI); - } - else if (parameters.get(OAuth2ParameterNames.REQUEST_URI).size() != 1) { - // Authorization Request - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI); - } - } - - if (!StringUtils.hasText(requestUri)) { - // response_type (REQUIRED) - String responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE); - if (!StringUtils.hasText(responseType) || parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE); - } - else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) { - throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE); - } - } - - String authorizationUri = request.getRequestURL().toString(); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); - } - - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { - principal = ANONYMOUS_AUTHENTICATION; - } - - // redirect_uri (OPTIONAL) - String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI); - } - - // scope (OPTIONAL) - Set scopes = null; - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE); - } - if (StringUtils.hasText(scope)) { - scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - // state (RECOMMENDED) - String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); - } - - // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) - String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE); - if (StringUtils.hasText(codeChallenge) && parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI); - } - - // code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE) - String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD); - if (StringUtils.hasText(codeChallengeMethod) - && parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI); - } - - // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) - if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) { - String prompt = parameters.getFirst("prompt"); - if (StringUtils.hasText(prompt) && parameters.get("prompt").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt"); - } - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.RESPONSE_TYPE) && !key.equals(OAuth2ParameterNames.CLIENT_ID) - && !key.equals(OAuth2ParameterNames.REDIRECT_URI) && !key.equals(OAuth2ParameterNames.SCOPE) - && !key.equals(OAuth2ParameterNames.STATE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - if (pushedAuthorizationRequest) { - return new OAuth2PushedAuthorizationRequestAuthenticationToken(authorizationUri, clientId, principal, - redirectUri, state, scopes, additionalParameters); - } - else { - return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationUri, clientId, principal, - redirectUri, state, scopes, additionalParameters); - } - } - - private boolean isPushedAuthorizationRequest(HttpServletRequest request) { - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - AuthorizationServerSettings authorizationServerSettings = authorizationServerContext - .getAuthorizationServerSettings(); - return request.getRequestURL() - .toString() - .toLowerCase(Locale.ROOT) - .endsWith(authorizationServerSettings.getPushedAuthorizationRequestEndpoint().toLowerCase(Locale.ROOT)); - } - - private static RequestMatcher createDefaultRequestMatcher() { - RequestMatcher getMethodMatcher = (request) -> "GET".equals(request.getMethod()); - RequestMatcher postMethodMatcher = (request) -> "POST".equals(request.getMethod()); - RequestMatcher responseTypeParameterMatcher = ( - request) -> request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null; - return new OrRequestMatcher(getMethodMatcher, - new AndRequestMatcher(postMethodMatcher, responseTypeParameterMatcher)); - } - - private static void throwError(String errorCode, String parameterName) { - throwError(errorCode, parameterName, DEFAULT_ERROR_URI); - } - - private static void throwError(String errorCode, String parameterName, String errorUri) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java deleted file mode 100644 index 8bca4ca2f..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.NegatedRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Authorization Consent from {@link HttpServletRequest} for the - * OAuth 2.0 Authorization Code Grant and then converts it to an - * {@link OAuth2AuthorizationConsentAuthenticationToken} used for authenticating the - * request. - * - * @author Joe Grandja - * @since 0.4.0 - * @see AuthenticationConverter - * @see OAuth2AuthorizationConsentAuthenticationToken - * @see OAuth2AuthorizationEndpointFilter - */ -public final class OAuth2AuthorizationConsentAuthenticationConverter implements AuthenticationConverter { - - private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; - - private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", - "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - private final RequestMatcher requestMatcher = createDefaultRequestMatcher(); - - @Override - public Authentication convert(HttpServletRequest request) { - if (!this.requestMatcher.matches(request)) { - return null; - } - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - String authorizationUri = request.getRequestURL().toString(); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); - } - - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { - principal = ANONYMOUS_AUTHENTICATION; - } - - // state (REQUIRED) - String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (!StringUtils.hasText(state) || parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); - } - - // scope (OPTIONAL) - Set scopes = null; - if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) { - scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE)); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.CLIENT_ID) && !key.equals(OAuth2ParameterNames.STATE) - && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - return new OAuth2AuthorizationConsentAuthenticationToken(authorizationUri, clientId, principal, state, scopes, - additionalParameters); - } - - private static RequestMatcher createDefaultRequestMatcher() { - RequestMatcher postMethodMatcher = (request) -> "POST".equals(request.getMethod()); - RequestMatcher responseTypeParameterMatcher = ( - request) -> request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null; - return new AndRequestMatcher(postMethodMatcher, new NegatedRequestMatcher(responseTypeParameterMatcher)); - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java deleted file mode 100644 index 2a1d29685..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Access Token Request from {@link HttpServletRequest} for the - * OAuth 2.0 Client Credentials Grant and then converts it to an - * {@link OAuth2ClientCredentialsAuthenticationToken} used for authenticating the - * authorization grant. - * - * @author Joe Grandja - * @since 0.1.2 - * @see AuthenticationConverter - * @see OAuth2ClientCredentialsAuthenticationToken - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2ClientCredentialsAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // grant_type (REQUIRED) - String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) { - return null; - } - - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - // scope (OPTIONAL) - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - Set requestedScopes = null; - if (StringUtils.hasText(scope)) { - requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - // Validate DPoP Proof HTTP Header (if available) - OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); - - return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScopes, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java deleted file mode 100644 index a64bd1570..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a Device Authorization Consent from {@link HttpServletRequest} for - * the OAuth 2.0 Device Authorization Grant and then converts it to an - * {@link OAuth2DeviceAuthorizationConsentAuthenticationToken} used for authenticating the - * request. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationConverter - * @see OAuth2DeviceAuthorizationConsentAuthenticationToken - * @see OAuth2DeviceVerificationEndpointFilter - */ -public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter implements AuthenticationConverter { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", - "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - @Override - public Authentication convert(HttpServletRequest request) { - if (!"POST".equals(request.getMethod()) || request.getParameter(OAuth2ParameterNames.STATE) == null) { - return null; - } - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - String authorizationUri = request.getRequestURL().toString(); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, ERROR_URI); - } - - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { - principal = ANONYMOUS_AUTHENTICATION; - } - - // user_code (REQUIRED) - String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE); - if (!OAuth2EndpointUtils.validateUserCode(userCode) - || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USER_CODE, ERROR_URI); - } - - // state (REQUIRED) - String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (!StringUtils.hasText(state) || parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, ERROR_URI); - } - - // scope (OPTIONAL) - Set scopes = null; - if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) { - scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE)); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.CLIENT_ID) && !key.equals(OAuth2ParameterNames.USER_CODE) - && !key.equals(OAuth2ParameterNames.STATE) && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - return new OAuth2DeviceAuthorizationConsentAuthenticationToken(authorizationUri, clientId, principal, - OAuth2EndpointUtils.normalizeUserCode(userCode), state, scopes, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java deleted file mode 100644 index 129c9c3a2..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceAuthorizationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a Device Authorization Request from {@link HttpServletRequest} for - * the OAuth 2.0 Device Authorization Grant and then converts it to an - * {@link OAuth2DeviceAuthorizationRequestAuthenticationToken} used for authenticating the - * request. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationConverter - * @see OAuth2DeviceAuthorizationRequestAuthenticationToken - * @see OAuth2DeviceAuthorizationEndpointFilter - */ -public final class OAuth2DeviceAuthorizationRequestAuthenticationConverter implements AuthenticationConverter { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc8628#section-3.1"; - - @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - String authorizationUri = request.getRequestURL().toString(); - - // scope (OPTIONAL) - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, ERROR_URI); - } - Set requestedScopes = null; - if (StringUtils.hasText(scope)) { - requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.CLIENT_ID) && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, authorizationUri, - requestedScopes, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java deleted file mode 100644 index d3490d05d..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a Device Access Token Request from {@link HttpServletRequest} for - * the OAuth 2.0 Device Authorization Grant and then converts it to an - * {@link OAuth2DeviceCodeAuthenticationToken} used for authenticating the authorization - * grant. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationConverter - * @see OAuth2DeviceCodeAuthenticationToken - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2DeviceCodeAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // grant_type (REQUIRED) - String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.DEVICE_CODE.getValue().equals(grantType)) { - return null; - } - - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - // device_code (REQUIRED) - String deviceCode = parameters.getFirst(OAuth2ParameterNames.DEVICE_CODE); - if (!StringUtils.hasText(deviceCode) || parameters.get(OAuth2ParameterNames.DEVICE_CODE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.DEVICE_CODE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.CLIENT_ID) - && !key.equals(OAuth2ParameterNames.DEVICE_CODE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - // Validate DPoP Proof HTTP Header (if available) - OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); - - return new OAuth2DeviceCodeAuthenticationToken(deviceCode, clientPrincipal, additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java deleted file mode 100644 index 1c311b498..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; - -/** - * Attempts to extract a user code from {@link HttpServletRequest} for the OAuth 2.0 - * Device Authorization Grant and then converts it to an - * {@link OAuth2DeviceVerificationAuthenticationToken} used for authenticating the - * request. - * - * @author Steve Riesenberg - * @since 1.1 - * @see AuthenticationConverter - * @see OAuth2DeviceVerificationAuthenticationToken - * @see OAuth2DeviceVerificationEndpointFilter - */ -public final class OAuth2DeviceVerificationAuthenticationConverter implements AuthenticationConverter { - - private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", - "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - @Override - public Authentication convert(HttpServletRequest request) { - if (!("GET".equals(request.getMethod()) || "POST".equals(request.getMethod()))) { - return null; - } - if (request.getParameter(OAuth2ParameterNames.STATE) != null - || request.getParameter(OAuth2ParameterNames.USER_CODE) == null) { - return null; - } - - MultiValueMap parameters = "GET".equals(request.getMethod()) - ? OAuth2EndpointUtils.getQueryParameters(request) : OAuth2EndpointUtils.getFormParameters(request); - - // user_code (REQUIRED) - String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE); - if (!OAuth2EndpointUtils.validateUserCode(userCode) - || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USER_CODE, ERROR_URI); - } - - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (principal == null) { - principal = ANONYMOUS_AUTHENTICATION; - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.USER_CODE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - return new OAuth2DeviceVerificationAuthenticationToken(principal, - OAuth2EndpointUtils.normalizeUserCode(userCode), additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java deleted file mode 100644 index e1bf7d478..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Utility methods for the OAuth 2.0 Protocol Endpoints. - * - * @author Joe Grandja - * @author Greg Li - * @since 0.1.2 - */ -final class OAuth2EndpointUtils { - - static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; - - private OAuth2EndpointUtils() { - } - - static MultiValueMap getFormParameters(HttpServletRequest request) { - Map parameterMap = request.getParameterMap(); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameterMap.forEach((key, values) -> { - String queryString = StringUtils.hasText(request.getQueryString()) ? request.getQueryString() : ""; - // If not query parameter then it's a form parameter - if (!queryString.contains(key) && values.length > 0) { - for (String value : values) { - parameters.add(key, value); - } - } - }); - return parameters; - } - - static MultiValueMap getQueryParameters(HttpServletRequest request) { - Map parameterMap = request.getParameterMap(); - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameterMap.forEach((key, values) -> { - String queryString = StringUtils.hasText(request.getQueryString()) ? request.getQueryString() : ""; - if (queryString.contains(key) && values.length > 0) { - for (String value : values) { - parameters.add(key, value); - } - } - }); - return parameters; - } - - static Map getParametersIfMatchesAuthorizationCodeGrantRequest(HttpServletRequest request, - String... exclusions) { - if (!matchesAuthorizationCodeGrantRequest(request)) { - return Collections.emptyMap(); - } - MultiValueMap multiValueParameters = "GET".equals(request.getMethod()) - ? getQueryParameters(request) : getFormParameters(request); - for (String exclusion : exclusions) { - multiValueParameters.remove(exclusion); - } - - Map parameters = new HashMap<>(); - multiValueParameters.forEach( - (key, value) -> parameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0]))); - - return parameters; - } - - static boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) { - return AuthorizationGrantType.AUTHORIZATION_CODE.getValue() - .equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) - && request.getParameter(OAuth2ParameterNames.CODE) != null; - } - - static boolean matchesPkceTokenRequest(HttpServletRequest request) { - return matchesAuthorizationCodeGrantRequest(request) - && request.getParameter(PkceParameterNames.CODE_VERIFIER) != null; - } - - static void validateAndAddDPoPParametersIfAvailable(HttpServletRequest request, - Map additionalParameters) { - final String dPoPProofHeaderName = OAuth2AccessToken.TokenType.DPOP.getValue(); - String dPoPProof = request.getHeader(dPoPProofHeaderName); - if (StringUtils.hasText(dPoPProof)) { - if (Collections.list(request.getHeaders(dPoPProofHeaderName)).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, dPoPProofHeaderName, ACCESS_TOKEN_REQUEST_ERROR_URI); - } - else { - additionalParameters.put("dpop_proof", dPoPProof); - additionalParameters.put("dpop_method", request.getMethod()); - additionalParameters.put("dpop_target_uri", request.getRequestURL().toString()); - } - } - } - - static void throwError(String errorCode, String parameterName, String errorUri) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throw new OAuth2AuthenticationException(error); - } - - static String normalizeUserCode(String userCode) { - Assert.hasText(userCode, "userCode cannot be empty"); - StringBuilder sb = new StringBuilder(userCode.toUpperCase(Locale.ENGLISH).replaceAll("[^A-Z\\d]+", "")); - Assert.isTrue(sb.length() == 8, "userCode must be exactly 8 alpha/numeric characters"); - sb.insert(4, '-'); - return sb.toString(); - } - - static boolean validateUserCode(String userCode) { - return (userCode != null && userCode.toUpperCase(Locale.ENGLISH).replaceAll("[^A-Z\\d]+", "").length() == 8); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandler.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandler.java deleted file mode 100644 index 1831cf56c..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.util.Assert; - -/** - * An implementation of an {@link AuthenticationFailureHandler} used for handling an - * {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error OAuth 2.0 - * Error Response}. - * - * @author Dmitriy Dubson - * @since 1.2 - * @see AuthenticationFailureHandler - * @see OAuth2ErrorHttpMessageConverter - */ -public final class OAuth2ErrorAuthenticationFailureHandler implements AuthenticationFailureHandler { - - private final Log logger = LogFactory.getLog(getClass()); - - private HttpMessageConverter errorResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authenticationException) throws IOException, ServletException { - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); - - if (authenticationException instanceof OAuth2AuthenticationException oauth2AuthenticationException) { - OAuth2Error error = oauth2AuthenticationException.getError(); - this.errorResponseConverter.write(error, null, httpResponse); - } - else { - if (this.logger.isWarnEnabled()) { - this.logger.warn(AuthenticationException.class.getSimpleName() + " must be of type " - + OAuth2AuthenticationException.class.getName() + " but was " - + authenticationException.getClass().getName()); - } - } - } - - /** - * Sets the {@link HttpMessageConverter} used for converting an {@link OAuth2Error} to - * an HTTP response. - * @param errorResponseConverter the {@link HttpMessageConverter} used for converting - * an {@link OAuth2Error} to an HTTP response - */ - public void setErrorResponseConverter(HttpMessageConverter errorResponseConverter) { - Assert.notNull(errorResponseConverter, "errorResponseConverter cannot be null"); - this.errorResponseConverter = errorResponseConverter; - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java deleted file mode 100644 index 740776494..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Access Token Request from {@link HttpServletRequest} for the - * OAuth 2.0 Refresh Token Grant and then converts it to an - * {@link OAuth2RefreshTokenAuthenticationToken} used for authenticating the authorization - * grant. - * - * @author Joe Grandja - * @since 0.1.2 - * @see AuthenticationConverter - * @see OAuth2RefreshTokenAuthenticationToken - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2RefreshTokenAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // grant_type (REQUIRED) - String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) { - return null; - } - - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - // refresh_token (REQUIRED) - String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN); - if (!StringUtils.hasText(refreshToken) || parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - // scope (OPTIONAL) - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - Set requestedScopes = null; - if (StringUtils.hasText(scope)) { - requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.REFRESH_TOKEN) - && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - // Validate DPoP Proof HTTP Header (if available) - OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); - - return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes, - additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java deleted file mode 100644 index 01a121afd..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Access Token Request from {@link HttpServletRequest} for the - * OAuth 2.0 Token Exchange Grant and then converts it to an - * {@link OAuth2TokenExchangeAuthenticationToken} used for authenticating the - * authorization grant. - * - * @author Steve Riesenberg - * @since 1.3 - * @see AuthenticationConverter - * @see OAuth2TokenExchangeAuthenticationToken - * @see OAuth2TokenEndpointFilter - */ -public final class OAuth2TokenExchangeAuthenticationConverter implements AuthenticationConverter { - - private static final String TOKEN_TYPE_IDENTIFIERS_URI = "https://datatracker.ietf.org/doc/html/rfc8693#section-3"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private static final Set SUPPORTED_TOKEN_TYPES = Set.of(ACCESS_TOKEN_TYPE_VALUE, JWT_TOKEN_TYPE_VALUE); - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // grant_type (REQUIRED) - String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.TOKEN_EXCHANGE.getValue().equals(grantType)) { - return null; - } - - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - // resource (OPTIONAL) - List resources = parameters.getOrDefault(OAuth2ParameterNames.RESOURCE, Collections.emptyList()); - if (!CollectionUtils.isEmpty(resources)) { - for (String resource : resources) { - if (!isValidUri(resource)) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESOURCE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - } - } - - // audience (OPTIONAL) - List audiences = parameters.getOrDefault(OAuth2ParameterNames.AUDIENCE, Collections.emptyList()); - - // scope (OPTIONAL) - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - Set requestedScopes = null; - if (StringUtils.hasText(scope)) { - requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - // requested_token_type (OPTIONAL) - String requestedTokenType = parameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE); - if (StringUtils.hasText(requestedTokenType)) { - if (parameters.get(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, - OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - validateTokenType(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, requestedTokenType); - } - else { - requestedTokenType = ACCESS_TOKEN_TYPE_VALUE; - } - - // subject_token (REQUIRED) - String subjectToken = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN); - if (!StringUtils.hasText(subjectToken) || parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - // subject_token_type (REQUIRED) - String subjectTokenType = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE); - if (!StringUtils.hasText(subjectTokenType) - || parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - else { - validateTokenType(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, subjectTokenType); - } - - // actor_token (OPTIONAL, REQUIRED if actor_token_type is provided) - String actorToken = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN); - if (StringUtils.hasText(actorToken) && parameters.get(OAuth2ParameterNames.ACTOR_TOKEN).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - // actor_token_type (OPTIONAL, REQUIRED if actor_token is provided) - String actorTokenType = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE); - if (StringUtils.hasText(actorTokenType)) { - if (parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE).size() != 1) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN_TYPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - validateTokenType(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, actorTokenType); - } - - if (!StringUtils.hasText(actorToken) && StringUtils.hasText(actorTokenType)) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - else if (StringUtils.hasText(actorToken) && !StringUtils.hasText(actorTokenType)) { - OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN_TYPE, - OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.RESOURCE) - && !key.equals(OAuth2ParameterNames.AUDIENCE) - && !key.equals(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) - && !key.equals(OAuth2ParameterNames.SUBJECT_TOKEN) - && !key.equals(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) - && !key.equals(OAuth2ParameterNames.ACTOR_TOKEN) - && !key.equals(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) && !key.equals(OAuth2ParameterNames.SCOPE)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - // Validate DPoP Proof HTTP Header (if available) - OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); - - return new OAuth2TokenExchangeAuthenticationToken(requestedTokenType, subjectToken, subjectTokenType, - clientPrincipal, actorToken, actorTokenType, new LinkedHashSet<>(resources), - new LinkedHashSet<>(audiences), requestedScopes, additionalParameters); - } - - private static void validateTokenType(String parameterName, String tokenTypeValue) { - if (!SUPPORTED_TOKEN_TYPES.contains(tokenTypeValue)) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE, - String.format("OAuth 2.0 Token Exchange parameter: %s", parameterName), TOKEN_TYPE_IDENTIFIERS_URI); - // @formatter:off - String message = String.format( - "OAuth 2.0 Token Exchange parameter: %s - " + - "The provided value is not supported by this authorization server. " + - "Supported values are %s and %s.", - parameterName, ACCESS_TOKEN_TYPE_VALUE, JWT_TOKEN_TYPE_VALUE); - // @formatter:on - throw new OAuth2AuthenticationException(error, message); - } - } - - private static boolean isValidUri(String uri) { - try { - URI validUri = new URI(uri); - return validUri.isAbsolute() && validUri.getFragment() == null; - } - catch (URISyntaxException ex) { - return false; - } - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java deleted file mode 100644 index 0f8b2625e..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract an Introspection Request from {@link HttpServletRequest} and then - * converts it to an {@link OAuth2TokenIntrospectionAuthenticationToken} used for - * authenticating the request. - * - * @author Gerardo Roza - * @author Joe Grandja - * @since 0.4.0 - * @see AuthenticationConverter - * @see OAuth2TokenIntrospectionAuthenticationToken - * @see OAuth2TokenIntrospectionEndpointFilter - */ -public final class OAuth2TokenIntrospectionAuthenticationConverter implements AuthenticationConverter { - - @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // token (REQUIRED) - String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); - if (!StringUtils.hasText(token) || parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); - } - - // token_type_hint (OPTIONAL) - String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); - if (StringUtils.hasText(tokenTypeHint) && parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); - } - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> { - if (!key.equals(OAuth2ParameterNames.TOKEN) && !key.equals(OAuth2ParameterNames.TOKEN_TYPE_HINT)) { - additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0])); - } - }); - - return new OAuth2TokenIntrospectionAuthenticationToken(token, clientPrincipal, tokenTypeHint, - additionalParameters); - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Introspection Parameter: " + parameterName, - "https://datatracker.ietf.org/doc/html/rfc7662#section-2.1"); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java deleted file mode 100644 index 1b357a3de..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a Revoke Token Request from {@link HttpServletRequest} and then - * converts it to an {@link OAuth2TokenRevocationAuthenticationToken} used for - * authenticating the request. - * - * @author Vivek Babu - * @author Joe Grandja - * @since 0.4.0 - * @see AuthenticationConverter - * @see OAuth2TokenRevocationAuthenticationToken - * @see OAuth2TokenRevocationEndpointFilter - */ -public final class OAuth2TokenRevocationAuthenticationConverter implements AuthenticationConverter { - - @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // token (REQUIRED) - String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); - if (!StringUtils.hasText(token) || parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); - } - - // token_type_hint (OPTIONAL) - String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); - if (StringUtils.hasText(tokenTypeHint) && parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); - } - - return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint); - } - - private static void throwError(String errorCode, String parameterName) { - OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Revocation Parameter: " + parameterName, - "https://datatracker.ietf.org/doc/html/rfc7009#section-2.1"); - throw new OAuth2AuthenticationException(error); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java deleted file mode 100644 index 0fdc56c61..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract the parameters from {@link HttpServletRequest} used for - * authenticating public clients using Proof Key for Code Exchange (PKCE). - * - * @author Joe Grandja - * @since 0.0.2 - * @see AuthenticationConverter - * @see OAuth2ClientAuthenticationToken - * @see OAuth2ClientAuthenticationFilter - * @see Proof Key for Code - * Exchange by OAuth Public Clients - */ -public final class PublicClientAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - if (!OAuth2EndpointUtils.matchesPkceTokenRequest(request)) { - return null; - } - - MultiValueMap parameters = "GET".equals(request.getMethod()) - ? OAuth2EndpointUtils.getQueryParameters(request) : OAuth2EndpointUtils.getFormParameters(request); - - // client_id (REQUIRED for public clients) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - // code_verifier (REQUIRED) - if (parameters.get(PkceParameterNames.CODE_VERIFIER).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - parameters.remove(OAuth2ParameterNames.CLIENT_ID); - - Map additionalParameters = new HashMap<>(); - parameters.forEach((key, value) -> additionalParameters.put(key, - (value.size() == 1) ? value.get(0) : value.toArray(new String[0]))); - - return new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.NONE, null, - additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java deleted file mode 100644 index 0c71f961b..000000000 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.security.cert.X509Certificate; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.lang.Nullable; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * Attempts to extract a client {@code X509Certificate} chain from - * {@link HttpServletRequest} and then converts to an - * {@link OAuth2ClientAuthenticationToken} used for authenticating the client using the - * {@code tls_client_auth} or {@code self_signed_tls_client_auth} method. - * - * @author Joe Grandja - * @since 1.3 - * @see AuthenticationConverter - * @see OAuth2ClientAuthenticationToken - * @see OAuth2ClientAuthenticationFilter - */ -public final class X509ClientCertificateAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - X509Certificate[] clientCertificateChain = (X509Certificate[]) request - .getAttribute("jakarta.servlet.request.X509Certificate"); - if (clientCertificateChain == null || clientCertificateChain.length == 0) { - return null; - } - - MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); - - // client_id (REQUIRED) - String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId)) { - return null; - } - - if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); - } - - Map additionalParameters = OAuth2EndpointUtils - .getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ID); - - ClientAuthenticationMethod clientAuthenticationMethod = (clientCertificateChain.length == 1) - ? ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH : ClientAuthenticationMethod.TLS_CLIENT_AUTH; - - return new OAuth2ClientAuthenticationToken(clientId, clientAuthenticationMethod, clientCertificateChain, - additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/main/resources/META-INF/spring/aot.factories b/oauth2-authorization-server/src/main/resources/META-INF/spring/aot.factories deleted file mode 100644 index a82dcb0ac..000000000 --- a/oauth2-authorization-server/src/main/resources/META-INF/spring/aot.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\ - org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerBeanRegistrationAotProcessor diff --git a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql deleted file mode 100644 index a11ff75c8..000000000 --- a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE oauth2_registered_client ( - id varchar(100) NOT NULL, - client_id varchar(100) NOT NULL, - client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - client_secret varchar(200) DEFAULT NULL, - client_secret_expires_at timestamp DEFAULT NULL, - client_name varchar(200) NOT NULL, - client_authentication_methods varchar(1000) NOT NULL, - authorization_grant_types varchar(1000) NOT NULL, - redirect_uris varchar(1000) DEFAULT NULL, - post_logout_redirect_uris varchar(1000) DEFAULT NULL, - scopes varchar(1000) NOT NULL, - client_settings varchar(2000) NOT NULL, - token_settings varchar(2000) NOT NULL, - PRIMARY KEY (id) -); diff --git a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql deleted file mode 100644 index 3020828ab..000000000 --- a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE oauth2_authorization_consent ( - registered_client_id varchar(100) NOT NULL, - principal_name varchar(200) NOT NULL, - authorities varchar(1000) NOT NULL, - PRIMARY KEY (registered_client_id, principal_name) -); diff --git a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql deleted file mode 100644 index 1528032cf..000000000 --- a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql +++ /dev/null @@ -1,41 +0,0 @@ -/* -IMPORTANT: - If using PostgreSQL, update ALL columns defined with 'blob' to 'text', - as PostgreSQL does not support the 'blob' data type. -*/ -CREATE TABLE oauth2_authorization ( - id varchar(100) NOT NULL, - registered_client_id varchar(100) NOT NULL, - principal_name varchar(200) NOT NULL, - authorization_grant_type varchar(100) NOT NULL, - authorized_scopes varchar(1000) DEFAULT NULL, - attributes blob DEFAULT NULL, - state varchar(500) DEFAULT NULL, - authorization_code_value blob DEFAULT NULL, - authorization_code_issued_at timestamp DEFAULT NULL, - authorization_code_expires_at timestamp DEFAULT NULL, - authorization_code_metadata blob DEFAULT NULL, - access_token_value blob DEFAULT NULL, - access_token_issued_at timestamp DEFAULT NULL, - access_token_expires_at timestamp DEFAULT NULL, - access_token_metadata blob DEFAULT NULL, - access_token_type varchar(100) DEFAULT NULL, - access_token_scopes varchar(1000) DEFAULT NULL, - oidc_id_token_value blob DEFAULT NULL, - oidc_id_token_issued_at timestamp DEFAULT NULL, - oidc_id_token_expires_at timestamp DEFAULT NULL, - oidc_id_token_metadata blob DEFAULT NULL, - refresh_token_value blob DEFAULT NULL, - refresh_token_issued_at timestamp DEFAULT NULL, - refresh_token_expires_at timestamp DEFAULT NULL, - refresh_token_metadata blob DEFAULT NULL, - user_code_value blob DEFAULT NULL, - user_code_issued_at timestamp DEFAULT NULL, - user_code_expires_at timestamp DEFAULT NULL, - user_code_metadata blob DEFAULT NULL, - device_code_value blob DEFAULT NULL, - device_code_issued_at timestamp DEFAULT NULL, - device_code_expires_at timestamp DEFAULT NULL, - device_code_metadata blob DEFAULT NULL, - PRIMARY KEY (id) -); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java deleted file mode 100644 index e75b6d45d..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2020-2021 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.jose; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.UUID; - -import javax.crypto.SecretKey; - -import com.nimbusds.jose.jwk.Curve; -import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.RSAKey; - -/** - * @author Joe Grandja - */ -public final class TestJwks { - - private static final KeyPairGenerator rsaKeyPairGenerator; - static { - try { - rsaKeyPairGenerator = KeyPairGenerator.getInstance("RSA"); - rsaKeyPairGenerator.initialize(2048); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - // @formatter:off - public static final RSAKey DEFAULT_RSA_JWK = - jwk( - TestKeys.DEFAULT_PUBLIC_KEY, - TestKeys.DEFAULT_PRIVATE_KEY - ).build(); - // @formatter:on - - // @formatter:off - public static final ECKey DEFAULT_EC_JWK = - jwk( - (ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(), - (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate() - ).build(); - // @formatter:on - - // @formatter:off - public static final OctetSequenceKey DEFAULT_SECRET_JWK = - jwk( - TestKeys.DEFAULT_SECRET_KEY - ).build(); - // @formatter:on - - private TestJwks() { - } - - public static RSAKey.Builder generateRsaJwk() { - KeyPair keyPair = rsaKeyPairGenerator.generateKeyPair(); - RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); - RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); - // @formatter:off - return jwk(publicKey, privateKey) - .keyID(UUID.randomUUID().toString()); - // @formatter:on - } - - public static RSAKey.Builder jwk(RSAPublicKey publicKey, RSAPrivateKey privateKey) { - // @formatter:off - return new RSAKey.Builder(publicKey) - .privateKey(privateKey) - .keyUse(KeyUse.SIGNATURE) - .keyID("rsa-jwk-kid"); - // @formatter:on - } - - public static ECKey.Builder jwk(ECPublicKey publicKey, ECPrivateKey privateKey) { - // @formatter:off - Curve curve = Curve.forECParameterSpec(publicKey.getParams()); - return new ECKey.Builder(curve, publicKey) - .privateKey(privateKey) - .keyUse(KeyUse.SIGNATURE) - .keyID("ec-jwk-kid"); - // @formatter:on - } - - public static OctetSequenceKey.Builder jwk(SecretKey secretKey) { - // @formatter:off - return new OctetSequenceKey.Builder(secretKey) - .keyUse(KeyUse.SIGNATURE) - .keyID("secret-jwk-kid"); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java deleted file mode 100644 index 39e16e9f2..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2020-2022 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.jose; - -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.ECFieldFp; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.EllipticCurve; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -/** - * @author Joe Grandja - */ -public final class TestKeys { - - public static final KeyFactory kf; - static { - try { - kf = KeyFactory.getInstance("RSA"); - } - catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException(ex); - } - } - public static final String DEFAULT_ENCODED_SECRET_KEY = "bCzY/M48bbkwBEWjmNSIEPfwApcvXOnkCxORBEbPr+4="; - - public static final SecretKey DEFAULT_SECRET_KEY = new SecretKeySpec( - Base64.getDecoder().decode(DEFAULT_ENCODED_SECRET_KEY), "AES"); - - // @formatter:off - public static final String DEFAULT_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd" - + "7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv" - + "c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6" - + "iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2" - + "kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o" - + "RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj" - + "KwIDAQAB"; - // @formatter:on - - public static final RSAPublicKey DEFAULT_PUBLIC_KEY; - static { - X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PUBLIC_KEY)); - try { - DEFAULT_PUBLIC_KEY = (RSAPublicKey) kf.generatePublic(spec); - } - catch (InvalidKeySpecException ex) { - throw new IllegalArgumentException(ex); - } - } - - // @formatter:off - public static final String DEFAULT_RSA_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA" - + "iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM" - + "g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK" - + "LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF" - + "oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc" - + "3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn" - + "+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE" - + "E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek" - + "lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG" - + "mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7" - + "62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0" - + "bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA" - + "+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH" - + "Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA" - + "8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd" - + "I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY" - + "QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d" - + "rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk" - + "HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA" - + "Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN" - + "HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a" - + "FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF" - + "snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H" - + "c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM" - + "TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR" - + "47jndeyIaMTNETEmOnms+as17g=="; - // @formatter:on - - public static final RSAPrivateKey DEFAULT_PRIVATE_KEY; - static { - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PRIVATE_KEY)); - try { - DEFAULT_PRIVATE_KEY = (RSAPrivateKey) kf.generatePrivate(spec); - } - catch (InvalidKeySpecException ex) { - throw new IllegalArgumentException(ex); - } - } - - public static final KeyPair DEFAULT_RSA_KEY_PAIR = new KeyPair(DEFAULT_PUBLIC_KEY, DEFAULT_PRIVATE_KEY); - - public static final KeyPair DEFAULT_EC_KEY_PAIR = generateEcKeyPair(); - - static KeyPair generateEcKeyPair() { - EllipticCurve ellipticCurve = new EllipticCurve( - new ECFieldFp(new BigInteger( - "115792089210356248762697446949407573530086143415290314195533631308867097853951")), - new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"), - new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291")); - ECPoint ecPoint = new ECPoint( - new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"), - new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109")); - ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, ecPoint, - new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), 1); - - KeyPair keyPair; - try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); - keyPairGenerator.initialize(ecParameterSpec); - keyPair = keyPairGenerator.generateKeyPair(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - return keyPair; - } - - private TestKeys() { - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwsHeaders.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwsHeaders.java deleted file mode 100644 index a41b0833f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwsHeaders.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2020-2022 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.jwt; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; - -/** - * @author Joe Grandja - */ -public final class TestJwsHeaders { - - private TestJwsHeaders() { - } - - public static JwsHeader.Builder jwsHeader() { - return jwsHeader(SignatureAlgorithm.RS256); - } - - public static JwsHeader.Builder jwsHeader(SignatureAlgorithm signatureAlgorithm) { - // @formatter:off - return JwsHeader.with(signatureAlgorithm) - .jwkSetUrl("https://provider.com/oauth2/jwks") - .jwk(rsaJwk()) - .keyId("keyId") - .x509Url("https://provider.com/oauth2/x509") - .x509CertificateChain(Arrays.asList("x509Cert1", "x509Cert2")) - .x509SHA1Thumbprint("x509SHA1Thumbprint") - .x509SHA256Thumbprint("x509SHA256Thumbprint") - .type("JWT") - .contentType("jwt-content-type") - .header("custom-header-name", "custom-header-value"); - // @formatter:on - } - - private static Map rsaJwk() { - Map rsaJwk = new HashMap<>(); - rsaJwk.put("kty", "RSA"); - rsaJwk.put("n", "modulus"); - rsaJwk.put("e", "exponent"); - return rsaJwk; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java deleted file mode 100644 index 2dc5a8a40..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020-2021 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.jwt; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; - -/** - * @author Joe Grandja - */ -public final class TestJwtClaimsSets { - - private TestJwtClaimsSets() { - } - - public static JwtClaimsSet.Builder jwtClaimsSet() { - String issuer = "https://provider.com"; - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - - // @formatter:off - return JwtClaimsSet.builder() - .issuer(issuer) - .subject("subject") - .audience(Collections.singletonList("client-1")) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .id("jti") - .claim("custom-claim-name", "custom-claim-value"); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentServiceTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentServiceTests.java deleted file mode 100644 index a9fefc70b..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentServiceTests.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link InMemoryOAuth2AuthorizationConsentService}. - * - * @author Daniel Garnier-Moiroux - */ -public class InMemoryOAuth2AuthorizationConsentServiceTests { - - private static final String REGISTERED_CLIENT_ID = "registered-client-id"; - - private static final String PRINCIPAL_NAME = "principal-name"; - - private static final OAuth2AuthorizationConsent AUTHORIZATION_CONSENT = OAuth2AuthorizationConsent - .withId(REGISTERED_CLIENT_ID, PRINCIPAL_NAME) - .authority(new SimpleGrantedAuthority("some.authority")) - .build(); - - private InMemoryOAuth2AuthorizationConsentService authorizationConsentService; - - @BeforeEach - public void setUp() { - this.authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService(); - this.authorizationConsentService.save(AUTHORIZATION_CONSENT); - } - - @Test - public void constructorVarargsWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((OAuth2AuthorizationConsent) null)) - .withMessage("authorizationConsent cannot be null"); - } - - @Test - public void constructorListWhenAuthorizationConsentsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((List) null)) - .withMessage("authorizationConsents cannot be null"); - } - - @Test - public void constructorWhenDuplicateAuthorizationConsentsThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy( - () -> new InMemoryOAuth2AuthorizationConsentService(AUTHORIZATION_CONSENT, AUTHORIZATION_CONSENT)) - .withMessage( - "The authorizationConsent must be unique. Found duplicate, with registered client id: [registered-client-id] and principal name: [principal-name]"); - } - - @Test - public void saveWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.authorizationConsentService.save(null)) - .withMessage("authorizationConsent cannot be null"); - } - - @Test - public void saveWhenAuthorizationConsentNewThenSaved() { - OAuth2AuthorizationConsent expectedAuthorizationConsent = OAuth2AuthorizationConsent - .withId("new-client", "new-principal") - .authority(new SimpleGrantedAuthority("new.authority")) - .build(); - - this.authorizationConsentService.save(expectedAuthorizationConsent); - - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById("new-client", - "new-principal"); - assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent); - } - - @Test - public void saveWhenAuthorizationConsentExistsThenUpdated() { - OAuth2AuthorizationConsent expectedAuthorizationConsent = OAuth2AuthorizationConsent.from(AUTHORIZATION_CONSENT) - .authority(new SimpleGrantedAuthority("new.authority")) - .build(); - - this.authorizationConsentService.save(expectedAuthorizationConsent); - - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent); - assertThat(authorizationConsent).isNotEqualTo(AUTHORIZATION_CONSENT); - } - - @Test - public void removeWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.authorizationConsentService.remove(null)) - .withMessage("authorizationConsent cannot be null"); - } - - @Test - public void removeWhenAuthorizationConsentProvidedThenRemoved() { - this.authorizationConsentService.remove(AUTHORIZATION_CONSENT); - assertThat(this.authorizationConsentService.findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), - AUTHORIZATION_CONSENT.getPrincipalName())) - .isNull(); - } - - @Test - public void findByIdWhenRegisteredClientIdNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizationConsentService.findById(null, "some-user")) - .withMessage("registeredClientId cannot be empty"); - } - - @Test - public void findByIdWhenPrincipalNameNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizationConsentService.findById("some-client", null)) - .withMessage("principalName cannot be empty"); - } - - @Test - public void findByIdWhenAuthorizationConsentExistsThenFound() { - assertThat(this.authorizationConsentService.findById(REGISTERED_CLIENT_ID, PRINCIPAL_NAME)) - .isEqualTo(AUTHORIZATION_CONSENT); - } - - @Test - public void findByIdWhenAuthorizationConsentDoesNotExistThenNull() { - this.authorizationConsentService.save(AUTHORIZATION_CONSENT); - assertThat(this.authorizationConsentService.findById("unknown-client", PRINCIPAL_NAME)).isNull(); - assertThat(this.authorizationConsentService.findById(REGISTERED_CLIENT_ID, "unknown-user")).isNull(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationServiceTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationServiceTests.java deleted file mode 100644 index 7ea97e89c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationServiceTests.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link InMemoryOAuth2AuthorizationService}. - * - * @author Krisztian Toth - * @author Joe Grandja - */ -public class InMemoryOAuth2AuthorizationServiceTests { - - private static final String ID = "id"; - - private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build(); - - private static final String PRINCIPAL_NAME = "principal"; - - private static final AuthorizationGrantType AUTHORIZATION_GRANT_TYPE = AuthorizationGrantType.AUTHORIZATION_CODE; - - private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plus(5, ChronoUnit.MINUTES)); - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private InMemoryOAuth2AuthorizationService authorizationService; - - @BeforeEach - public void setup() { - this.authorizationService = new InMemoryOAuth2AuthorizationService(); - } - - @Test - public void constructorVarargsWhenAuthorizationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new InMemoryOAuth2AuthorizationService((OAuth2Authorization) null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - } - - @Test - public void constructorListWhenAuthorizationsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new InMemoryOAuth2AuthorizationService((List) null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizations cannot be null"); - } - - @Test - public void constructorWhenDuplicateAuthorizationsThenThrowIllegalArgumentException() { - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - - assertThatThrownBy(() -> new InMemoryOAuth2AuthorizationService(authorization, authorization)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("The authorization must be unique. Found duplicate identifier: id"); - } - - @Test - public void saveWhenAuthorizationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authorizationService.save(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - } - - @Test - public void saveWhenAuthorizationNewThenSaved() { - OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(expectedAuthorization); - - OAuth2Authorization authorization = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(expectedAuthorization); - } - - // gh-222 - @Test - public void saveWhenAuthorizationExistsThenUpdated() { - OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(originalAuthorization); - - OAuth2Authorization authorization = this.authorizationService.findById(originalAuthorization.getId()); - assertThat(authorization).isEqualTo(originalAuthorization); - - OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization) - .attribute("custom-name-1", "custom-value-1") - .build(); - this.authorizationService.save(updatedAuthorization); - - authorization = this.authorizationService.findById(updatedAuthorization.getId()); - assertThat(authorization).isEqualTo(updatedAuthorization); - assertThat(authorization).isNotEqualTo(originalAuthorization); - } - - @Test - public void saveWhenInitializedAuthorizationsReachMaxThenOldestRemoved() { - int maxInitializedAuthorizations = 5; - InMemoryOAuth2AuthorizationService authorizationService = new InMemoryOAuth2AuthorizationService( - maxInitializedAuthorizations); - - OAuth2Authorization initialAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID + "-initial") - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .attribute(OAuth2ParameterNames.STATE, "state-initial") - .build(); - authorizationService.save(initialAuthorization); - - OAuth2Authorization authorization = authorizationService.findById(initialAuthorization.getId()); - assertThat(authorization).isEqualTo(initialAuthorization); - authorization = authorizationService.findByToken(initialAuthorization.getAttribute(OAuth2ParameterNames.STATE), - STATE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(initialAuthorization); - - for (int i = 0; i < maxInitializedAuthorizations; i++) { - authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID + "-" + i) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .attribute(OAuth2ParameterNames.STATE, "state-" + i) - .build(); - authorizationService.save(authorization); - } - - authorization = authorizationService.findById(initialAuthorization.getId()); - assertThat(authorization).isNull(); - authorization = authorizationService.findByToken(initialAuthorization.getAttribute(OAuth2ParameterNames.STATE), - STATE_TOKEN_TYPE); - assertThat(authorization).isNull(); - } - - @Test - public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authorizationService.remove(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - } - - @Test - public void removeWhenAuthorizationProvidedThenRemoved() { - OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - - this.authorizationService.save(expectedAuthorization); - OAuth2Authorization authorization = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(expectedAuthorization); - - this.authorizationService.remove(expectedAuthorization); - authorization = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isNull(); - } - - @Test - public void findByIdWhenIdNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authorizationService.findById(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("id cannot be empty"); - } - - @Test - public void findByTokenWhenTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authorizationService.findByToken(null, AUTHORIZATION_CODE_TOKEN_TYPE)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("token cannot be empty"); - } - - @Test - public void findByTokenWhenStateExistsThenFound() { - String state = "state"; - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .attribute(OAuth2ParameterNames.STATE, state) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(state, null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenAuthorizationCodeExistsThenFound() { - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenAccessTokenExistsThenFound() { - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - Instant.now().minusSeconds(60), Instant.now()); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .accessToken(accessToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(accessToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenIdTokenExistsThenFound() { - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject("subject") - .issuedAt(Instant.now().minusSeconds(60)) - .expiresAt(Instant.now()) - .build(); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(idToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(idToken.getTokenValue(), - ID_TOKEN_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(idToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenRefreshTokenExistsThenFound() { - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .refreshToken(refreshToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(refreshToken.getTokenValue(), - OAuth2TokenType.REFRESH_TOKEN); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(refreshToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenWrongTokenTypeThenNotFound() { - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .refreshToken(refreshToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(refreshToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN); - assertThat(result).isNull(); - } - - @Test - public void findByTokenWhenTokenDoesNotExistThenNull() { - OAuth2Authorization result = this.authorizationService.findByToken("access-token", - OAuth2TokenType.ACCESS_TOKEN); - assertThat(result).isNull(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentServiceTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentServiceTests.java deleted file mode 100644 index f104c9054..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentServiceTests.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link JdbcOAuth2AuthorizationConsentService}. - * - * @author Ovidiu Popa - */ -public class JdbcOAuth2AuthorizationConsentServiceTests { - - private static final String OAUTH2_AUTHORIZATION_CONSENT_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql"; - - private static final String CUSTOM_OAUTH2_AUTHORIZATION_CONSENT_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-consent-schema.sql"; - - private static final String PRINCIPAL_NAME = "principal-name"; - - private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build(); - - private static final OAuth2AuthorizationConsent AUTHORIZATION_CONSENT = OAuth2AuthorizationConsent - .withId(REGISTERED_CLIENT.getId(), PRINCIPAL_NAME) - .authority(new SimpleGrantedAuthority("SCOPE_scope1")) - .authority(new SimpleGrantedAuthority("SCOPE_scope2")) - .authority(new SimpleGrantedAuthority("SCOPE_scope3")) - .authority(new SimpleGrantedAuthority("authority-a")) - .authority(new SimpleGrantedAuthority("authority-b")) - .build(); - - private EmbeddedDatabase db; - - private JdbcOperations jdbcOperations; - - private RegisteredClientRepository registeredClientRepository; - - private JdbcOAuth2AuthorizationConsentService authorizationConsentService; - - @BeforeEach - public void setUp() { - this.db = createDb(); - this.jdbcOperations = new JdbcTemplate(this.db); - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationConsentService = new JdbcOAuth2AuthorizationConsentService(this.jdbcOperations, - this.registeredClientRepository); - } - - @AfterEach - public void tearDown() { - this.db.shutdown(); - } - - @Test - public void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new JdbcOAuth2AuthorizationConsentService(null, this.registeredClientRepository)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jdbcOperations cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new JdbcOAuth2AuthorizationConsentService(this.jdbcOperations, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - // @formatter:on - } - - @Test - public void setAuthorizationConsentRowMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationConsentService.setAuthorizationConsentRowMapper(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentRowMapper cannot be null"); - // @formatter:on - } - - @Test - public void setAuthorizationConsentParametersMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationConsentService.setAuthorizationConsentParametersMapper(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentParametersMapper cannot be null"); - // @formatter:on - } - - @Test - public void saveWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizationConsentService.save(null)) - .withMessage("authorizationConsent cannot be null"); - // @formatter:on - } - - @Test - public void saveWhenAuthorizationConsentNewThenSaved() { - OAuth2AuthorizationConsent expectedAuthorizationConsent = OAuth2AuthorizationConsent - .withId("new-client", "new-principal") - .authority(new SimpleGrantedAuthority("new.authority")) - .build(); - - RegisteredClient newRegisteredClient = TestRegisteredClients.registeredClient().id("new-client").build(); - - given(this.registeredClientRepository.findById(eq(newRegisteredClient.getId()))) - .willReturn(newRegisteredClient); - - this.authorizationConsentService.save(expectedAuthorizationConsent); - - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById("new-client", - "new-principal"); - assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent); - } - - @Test - public void saveWhenAuthorizationConsentExistsThenUpdated() { - OAuth2AuthorizationConsent expectedAuthorizationConsent = OAuth2AuthorizationConsent.from(AUTHORIZATION_CONSENT) - .authority(new SimpleGrantedAuthority("new.authority")) - .build(); - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - this.authorizationConsentService.save(expectedAuthorizationConsent); - - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent); - assertThat(authorizationConsent).isNotEqualTo(AUTHORIZATION_CONSENT); - } - - @Test - public void saveLoadAuthorizationConsentWhenCustomStrategiesSetThenCalled() throws Exception { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - JdbcOAuth2AuthorizationConsentService.OAuth2AuthorizationConsentRowMapper authorizationConsentRowMapper = spy( - new JdbcOAuth2AuthorizationConsentService.OAuth2AuthorizationConsentRowMapper( - this.registeredClientRepository)); - this.authorizationConsentService.setAuthorizationConsentRowMapper(authorizationConsentRowMapper); - JdbcOAuth2AuthorizationConsentService.OAuth2AuthorizationConsentParametersMapper authorizationConsentParametersMapper = spy( - new JdbcOAuth2AuthorizationConsentService.OAuth2AuthorizationConsentParametersMapper()); - this.authorizationConsentService.setAuthorizationConsentParametersMapper(authorizationConsentParametersMapper); - - this.authorizationConsentService.save(AUTHORIZATION_CONSENT); - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(authorizationConsent).isEqualTo(AUTHORIZATION_CONSENT); - verify(authorizationConsentRowMapper).mapRow(any(), anyInt()); - verify(authorizationConsentParametersMapper).apply(any()); - } - - @Test - public void removeWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.authorizationConsentService.remove(null)) - .withMessage("authorizationConsent cannot be null"); - } - - @Test - public void removeWhenAuthorizationConsentProvidedThenRemoved() { - this.authorizationConsentService.remove(AUTHORIZATION_CONSENT); - assertThat(this.authorizationConsentService.findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), - AUTHORIZATION_CONSENT.getPrincipalName())) - .isNull(); - } - - @Test - public void findByIdWhenRegisteredClientIdNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizationConsentService.findById(null, "some-user")) - .withMessage("registeredClientId cannot be empty"); - } - - @Test - public void findByIdWhenPrincipalNameNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authorizationConsentService.findById("some-client", null)) - .withMessage("principalName cannot be empty"); - } - - @Test - public void findByIdWhenAuthorizationConsentExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - this.authorizationConsentService.save(AUTHORIZATION_CONSENT); - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(authorizationConsent).isNotNull(); - } - - @Test - public void findByIdWhenAuthorizationConsentDoesNotExistThenNull() { - this.authorizationConsentService.save(AUTHORIZATION_CONSENT); - assertThat(this.authorizationConsentService.findById("unknown-client", PRINCIPAL_NAME)).isNull(); - assertThat(this.authorizationConsentService.findById(REGISTERED_CLIENT.getId(), "unknown-user")).isNull(); - } - - @Test - public void tableDefinitionWhenCustomThenAbleToOverride() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - EmbeddedDatabase db = createDb(CUSTOM_OAUTH2_AUTHORIZATION_CONSENT_SCHEMA_SQL_RESOURCE); - OAuth2AuthorizationConsentService authorizationConsentService = new CustomJdbcOAuth2AuthorizationConsentService( - new JdbcTemplate(db), this.registeredClientRepository); - authorizationConsentService.save(AUTHORIZATION_CONSENT); - OAuth2AuthorizationConsent foundAuthorizationConsent1 = authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(foundAuthorizationConsent1).isEqualTo(AUTHORIZATION_CONSENT); - authorizationConsentService.remove(AUTHORIZATION_CONSENT); - OAuth2AuthorizationConsent foundAuthorizationConsent2 = authorizationConsentService - .findById(AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()); - assertThat(foundAuthorizationConsent2).isNull(); - db.shutdown(); - } - - private static EmbeddedDatabase createDb() { - return createDb(OAUTH2_AUTHORIZATION_CONSENT_SCHEMA_SQL_RESOURCE); - } - - private static EmbeddedDatabase createDb(String schema) { - // @formatter:off - return new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript(schema) - .build(); - // @formatter:on - } - - private static final class CustomJdbcOAuth2AuthorizationConsentService - extends JdbcOAuth2AuthorizationConsentService { - - // @formatter:off - private static final String COLUMN_NAMES = "registeredClientId, " - + "principalName, " - + "authorities"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2AuthorizationConsent"; - - private static final String PK_FILTER = "registeredClientId = ? AND principalName = ?"; - - // @formatter:off - private static final String LOAD_AUTHORIZATION_CONSENT_SQL = "SELECT " + COLUMN_NAMES - + " FROM " + TABLE_NAME - + " WHERE " + PK_FILTER; - // @formatter:on - - // @formatter:off - private static final String SAVE_AUTHORIZATION_CONSENT_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)"; - // @formatter:on - - private static final String REMOVE_AUTHORIZATION_CONSENT_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " - + PK_FILTER; - - private CustomJdbcOAuth2AuthorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - super(jdbcOperations, registeredClientRepository); - setAuthorizationConsentRowMapper(new CustomOAuth2AuthorizationConsentRowMapper(registeredClientRepository)); - } - - @Override - public void save(OAuth2AuthorizationConsent authorizationConsent) { - List parameters = getAuthorizationConsentParametersMapper().apply(authorizationConsent); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - getJdbcOperations().update(SAVE_AUTHORIZATION_CONSENT_SQL, pss); - } - - @Override - public void remove(OAuth2AuthorizationConsent authorizationConsent) { - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()), - new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName()) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - getJdbcOperations().update(REMOVE_AUTHORIZATION_CONSENT_SQL, pss); - } - - @Override - public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, registeredClientId), - new SqlParameterValue(Types.VARCHAR, principalName) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - List result = getJdbcOperations().query(LOAD_AUTHORIZATION_CONSENT_SQL, pss, - getAuthorizationConsentRowMapper()); - return !result.isEmpty() ? result.get(0) : null; - } - - private static final class CustomOAuth2AuthorizationConsentRowMapper - extends JdbcOAuth2AuthorizationConsentService.OAuth2AuthorizationConsentRowMapper { - - private CustomOAuth2AuthorizationConsentRowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - } - - @Override - public OAuth2AuthorizationConsent mapRow(ResultSet rs, int rowNum) throws SQLException { - String registeredClientId = rs.getString("registeredClientId"); - RegisteredClient registeredClient = getRegisteredClientRepository().findById(registeredClientId); - if (registeredClient == null) { - throw new DataRetrievalFailureException("The RegisteredClient with id '" + registeredClientId - + "' was not found in the RegisteredClientRepository."); - } - - String principalName = rs.getString("principalName"); - - OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClientId, - principalName); - String authorizationConsentAuthorities = rs.getString("authorities"); - if (authorizationConsentAuthorities != null) { - for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsentAuthorities)) { - builder.authority(new SimpleGrantedAuthority(authority)); - } - } - return builder.build(); - } - - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java deleted file mode 100644 index 1624e876e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java +++ /dev/null @@ -1,875 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import com.fasterxml.jackson.core.type.TypeReference; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link JdbcOAuth2AuthorizationService}. - * - * @author Ovidiu Popa - * @author Steve Riesenberg - */ -public class JdbcOAuth2AuthorizationServiceTests { - - private static final String OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql"; - - private static final String CUSTOM_OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql"; - - private static final String OAUTH2_AUTHORIZATION_SCHEMA_CLOB_DATA_TYPE_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private static final OAuth2TokenType USER_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.USER_CODE); - - private static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); - - private static final String ID = "id"; - - private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build(); - - private static final String PRINCIPAL_NAME = "principal"; - - private static final AuthorizationGrantType AUTHORIZATION_GRANT_TYPE = AuthorizationGrantType.AUTHORIZATION_CODE; - - private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode("code", - Instant.now().truncatedTo(ChronoUnit.MILLIS), - Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS)); - - private EmbeddedDatabase db; - - private JdbcOperations jdbcOperations; - - private RegisteredClientRepository registeredClientRepository; - - private JdbcOAuth2AuthorizationService authorizationService; - - @BeforeEach - public void setUp() { - this.db = createDb(); - this.jdbcOperations = new JdbcTemplate(this.db); - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = new JdbcOAuth2AuthorizationService(this.jdbcOperations, - this.registeredClientRepository); - } - - @AfterEach - public void tearDown() { - this.db.shutdown(); - } - - @Test - public void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(null, this.registeredClientRepository)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jdbcOperations cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(this.jdbcOperations, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenLobHandlerIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new JdbcOAuth2AuthorizationService(this.jdbcOperations, this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("lobHandler cannot be null"); - // @formatter:on - } - - @Test - public void setAuthorizationRowMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.setAuthorizationRowMapper(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationRowMapper cannot be null"); - // @formatter:on - } - - @Test - public void setAuthorizationParametersMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.setAuthorizationParametersMapper(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationParametersMapper cannot be null"); - // @formatter:on - } - - @Test - public void saveWhenAuthorizationNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.save(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - // @formatter:on - } - - @Test - public void saveWhenAuthorizationNewThenSaved() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(expectedAuthorization); - - OAuth2Authorization authorization = this.authorizationService.findById(ID); - assertThat(authorization).isEqualTo(expectedAuthorization); - } - - @Test - public void saveWhenAuthorizationExistsThenUpdated() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(originalAuthorization); - - OAuth2Authorization authorization = this.authorizationService.findById(originalAuthorization.getId()); - assertThat(authorization).isEqualTo(originalAuthorization); - - OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization) - .attribute("custom-name-1", "custom-value-1") - .build(); - this.authorizationService.save(updatedAuthorization); - - authorization = this.authorizationService.findById(updatedAuthorization.getId()); - assertThat(authorization).isEqualTo(updatedAuthorization); - assertThat(authorization).isNotEqualTo(originalAuthorization); - } - - @Test - public void saveLoadAuthorizationWhenCustomStrategiesSetThenCalled() throws Exception { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - - RowMapper authorizationRowMapper = spy( - new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(this.registeredClientRepository)); - this.authorizationService.setAuthorizationRowMapper(authorizationRowMapper); - Function> authorizationParametersMapper = spy( - new JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper()); - this.authorizationService.setAuthorizationParametersMapper(authorizationParametersMapper); - - this.authorizationService.save(originalAuthorization); - OAuth2Authorization authorization = this.authorizationService.findById(originalAuthorization.getId()); - assertThat(authorization).isEqualTo(originalAuthorization); - verify(authorizationRowMapper).mapRow(any(), anyInt()); - verify(authorizationParametersMapper).apply(any()); - } - - @Test - public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.remove(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - // @formatter:on - } - - @Test - public void removeWhenAuthorizationProvidedThenRemoved() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2Authorization expectedAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - - this.authorizationService.save(expectedAuthorization); - OAuth2Authorization authorization = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(expectedAuthorization); - - this.authorizationService.remove(authorization); - authorization = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isNull(); - } - - @Test - public void findByIdWhenIdNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.findById(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("id cannot be empty"); - // @formatter:on - } - - @Test - public void findByIdWhenIdEmptyThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.findById(" ")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("id cannot be empty"); - // @formatter:on - } - - @Test - public void findByTokenWhenTokenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authorizationService.findByToken(null, AUTHORIZATION_CODE_TOKEN_TYPE)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("token cannot be empty"); - // @formatter:on - } - - @Test - public void findByTokenWhenStateExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - String state = "state"; - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .attribute(OAuth2ParameterNames.STATE, state) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(state, null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenAuthorizationCodeExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(AUTHORIZATION_CODE.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenAccessTokenExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS), - Instant.now().truncatedTo(ChronoUnit.MILLIS)); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .accessToken(accessToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(accessToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenIdTokenExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject("subject") - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(idToken.getTokenValue(), - ID_TOKEN_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(idToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenRefreshTokenExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", - Instant.now().truncatedTo(ChronoUnit.MILLIS), - Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS)); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .refreshToken(refreshToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(refreshToken.getTokenValue(), - OAuth2TokenType.REFRESH_TOKEN); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(refreshToken.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenDeviceCodeExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2DeviceCode deviceCode = new OAuth2DeviceCode("device-code", Instant.now().truncatedTo(ChronoUnit.MILLIS), - Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS)); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(deviceCode) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(deviceCode.getTokenValue(), - DEVICE_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(deviceCode.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenUserCodeExistsThenFound() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - OAuth2UserCode userCode = new OAuth2UserCode("user-code", Instant.now().truncatedTo(ChronoUnit.MILLIS), - Instant.now().plus(5, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MILLIS)); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(userCode) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(userCode.getTokenValue(), - USER_CODE_TOKEN_TYPE); - assertThat(authorization).isEqualTo(result); - result = this.authorizationService.findByToken(userCode.getTokenValue(), null); - assertThat(authorization).isEqualTo(result); - } - - @Test - public void findByTokenWhenWrongTokenTypeThenNotFound() { - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", - Instant.now().truncatedTo(ChronoUnit.MILLIS)); - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .refreshToken(refreshToken) - .build(); - this.authorizationService.save(authorization); - - OAuth2Authorization result = this.authorizationService.findByToken(refreshToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN); - assertThat(result).isNull(); - } - - @Test - public void findByTokenWhenTokenDoesNotExistThenNull() { - OAuth2Authorization result = this.authorizationService.findByToken("access-token", - OAuth2TokenType.ACCESS_TOKEN); - assertThat(result).isNull(); - } - - @Test - public void tableDefinitionWhenCustomThenAbleToOverride() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - EmbeddedDatabase db = createDb(CUSTOM_OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE); - OAuth2AuthorizationService authorizationService = new CustomJdbcOAuth2AuthorizationService(new JdbcTemplate(db), - this.registeredClientRepository); - String state = "state"; - OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .attribute(OAuth2ParameterNames.STATE, state) - .token(AUTHORIZATION_CODE) - .build(); - authorizationService.save(originalAuthorization); - OAuth2Authorization foundAuthorization1 = authorizationService.findById(originalAuthorization.getId()); - assertThat(foundAuthorization1).isEqualTo(originalAuthorization); - OAuth2Authorization foundAuthorization2 = authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(foundAuthorization2).isEqualTo(originalAuthorization); - db.shutdown(); - } - - @Test - public void tableDefinitionWhenClobSqlTypeThenAuthorizationUpdated() { - given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); - - EmbeddedDatabase db = createDb(OAUTH2_AUTHORIZATION_SCHEMA_CLOB_DATA_TYPE_SQL_RESOURCE); - OAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(new JdbcTemplate(db), - this.registeredClientRepository); - OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .build(); - authorizationService.save(originalAuthorization); - - OAuth2Authorization authorization = authorizationService.findById(originalAuthorization.getId()); - assertThat(authorization).isEqualTo(originalAuthorization); - - OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization) - .attribute("custom-name-1", "custom-value-1") - .build(); - authorizationService.save(updatedAuthorization); - - authorization = authorizationService.findById(updatedAuthorization.getId()); - assertThat(authorization).isEqualTo(updatedAuthorization); - assertThat(authorization).isNotEqualTo(originalAuthorization); - db.shutdown(); - } - - private static EmbeddedDatabase createDb() { - return createDb(OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE); - } - - private static EmbeddedDatabase createDb(String schema) { - // @formatter:off - return new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript(schema) - .build(); - // @formatter:on - } - - private static final class CustomJdbcOAuth2AuthorizationService extends JdbcOAuth2AuthorizationService { - - // @formatter:off - private static final String COLUMN_NAMES = "id, " - + "registeredClientId, " - + "principalName, " - + "authorizationGrantType, " - + "authorizedScopes, " - + "attributes, " - + "state, " - + "authorizationCodeValue, " - + "authorizationCodeIssuedAt, " - + "authorizationCodeExpiresAt," - + "authorizationCodeMetadata," - + "accessTokenValue," - + "accessTokenIssuedAt," - + "accessTokenExpiresAt," - + "accessTokenMetadata," - + "accessTokenType," - + "accessTokenScopes," - + "oidcIdTokenValue," - + "oidcIdTokenIssuedAt," - + "oidcIdTokenExpiresAt," - + "oidcIdTokenMetadata," - + "refreshTokenValue," - + "refreshTokenIssuedAt," - + "refreshTokenExpiresAt," - + "refreshTokenMetadata," - + "userCodeValue," - + "userCodeIssuedAt," - + "userCodeExpiresAt," - + "userCodeMetadata," - + "deviceCodeValue," - + "deviceCodeIssuedAt," - + "deviceCodeExpiresAt," - + "deviceCodeMetadata"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2Authorization"; - - private static final String PK_FILTER = "id = ?"; - - private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorizationCodeValue = ? OR " - + "accessTokenValue = ? OR oidcIdTokenValue = ? OR refreshTokenValue = ? OR userCodeValue = ? OR " - + "deviceCodeValue = ?"; - - // @formatter:off - private static final String LOAD_AUTHORIZATION_SQL = "SELECT " + COLUMN_NAMES - + " FROM " + TABLE_NAME - + " WHERE "; - // @formatter:on - - // @formatter:off - private static final String SAVE_AUTHORIZATION_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - // @formatter:on - - private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; - - private CustomJdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - super(jdbcOperations, registeredClientRepository); - setAuthorizationRowMapper(new CustomOAuth2AuthorizationRowMapper(registeredClientRepository)); - setAuthorizationParametersMapper(new CustomOAuth2AuthorizationParametersMapper()); - } - - @Override - public void save(OAuth2Authorization authorization) { - List parameters = getAuthorizationParametersMapper().apply(authorization); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - getJdbcOperations().update(SAVE_AUTHORIZATION_SQL, pss); - } - - @Override - public void remove(OAuth2Authorization authorization) { - SqlParameterValue[] parameters = new SqlParameterValue[] { - new SqlParameterValue(Types.VARCHAR, authorization.getId()) }; - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); - getJdbcOperations().update(REMOVE_AUTHORIZATION_SQL, pss); - } - - @Override - public OAuth2Authorization findById(String id) { - return findBy(PK_FILTER, id); - } - - @Override - public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { - return findBy(UNKNOWN_TOKEN_TYPE_FILTER, token, token, token, token, token, token, token); - } - - private OAuth2Authorization findBy(String filter, Object... args) { - List result = getJdbcOperations().query(LOAD_AUTHORIZATION_SQL + filter, - getAuthorizationRowMapper(), args); - return !result.isEmpty() ? result.get(0) : null; - } - - private static final class CustomOAuth2AuthorizationRowMapper - extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - private CustomOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - } - - @Override - @SuppressWarnings("unchecked") - public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException { - String registeredClientId = rs.getString("registeredClientId"); - RegisteredClient registeredClient = getRegisteredClientRepository().findById(registeredClientId); - if (registeredClient == null) { - throw new DataRetrievalFailureException("The RegisteredClient with id '" + registeredClientId - + "' was not found in the RegisteredClientRepository."); - } - - OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient); - String id = rs.getString("id"); - String principalName = rs.getString("principalName"); - String authorizationGrantType = rs.getString("authorizationGrantType"); - Set authorizedScopes = Collections.emptySet(); - String authorizedScopesString = rs.getString("authorizedScopes"); - if (authorizedScopesString != null) { - authorizedScopes = StringUtils.commaDelimitedListToSet(authorizedScopesString); - } - Map attributes = parseMap(rs.getString("attributes")); - - builder.id(id) - .principalName(principalName) - .authorizationGrantType(new AuthorizationGrantType(authorizationGrantType)) - .authorizedScopes(authorizedScopes) - .attributes((attrs) -> attrs.putAll(attributes)); - - String state = rs.getString("state"); - if (StringUtils.hasText(state)) { - builder.attribute(OAuth2ParameterNames.STATE, state); - } - - String tokenValue = rs.getString("authorizationCodeValue"); - Instant tokenIssuedAt; - Instant tokenExpiresAt; - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("authorizationCodeIssuedAt").toInstant(); - tokenExpiresAt = rs.getTimestamp("authorizationCodeExpiresAt").toInstant(); - Map authorizationCodeMetadata = parseMap(rs.getString("authorizationCodeMetadata")); - - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(tokenValue, tokenIssuedAt, - tokenExpiresAt); - builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata)); - } - - tokenValue = rs.getString("accessTokenValue"); - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("accessTokenIssuedAt").toInstant(); - tokenExpiresAt = rs.getTimestamp("accessTokenExpiresAt").toInstant(); - Map accessTokenMetadata = parseMap(rs.getString("accessTokenMetadata")); - OAuth2AccessToken.TokenType tokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue() - .equalsIgnoreCase(rs.getString("accessTokenType"))) { - tokenType = OAuth2AccessToken.TokenType.BEARER; - } - - Set scopes = Collections.emptySet(); - String accessTokenScopes = rs.getString("accessTokenScopes"); - if (accessTokenScopes != null) { - scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes); - } - OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, tokenValue, tokenIssuedAt, - tokenExpiresAt, scopes); - builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata)); - } - - tokenValue = rs.getString("oidcIdTokenValue"); - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("oidcIdTokenIssuedAt").toInstant(); - tokenExpiresAt = rs.getTimestamp("oidcIdTokenExpiresAt").toInstant(); - Map oidcTokenMetadata = parseMap(rs.getString("oidcIdTokenMetadata")); - - OidcIdToken oidcToken = new OidcIdToken(tokenValue, tokenIssuedAt, tokenExpiresAt, - (Map) oidcTokenMetadata - .get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME)); - builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata)); - } - - tokenValue = rs.getString("refreshTokenValue"); - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("refreshTokenIssuedAt").toInstant(); - tokenExpiresAt = null; - Timestamp refreshTokenExpiresAt = rs.getTimestamp("refreshTokenExpiresAt"); - if (refreshTokenExpiresAt != null) { - tokenExpiresAt = refreshTokenExpiresAt.toInstant(); - } - Map refreshTokenMetadata = parseMap(rs.getString("refreshTokenMetadata")); - - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(tokenValue, tokenIssuedAt, tokenExpiresAt); - builder.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata)); - } - - tokenValue = rs.getString("userCodeValue"); - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("userCodeIssuedAt").toInstant(); - tokenExpiresAt = rs.getTimestamp("userCodeExpiresAt").toInstant(); - Map userCodeMetadata = parseMap(rs.getString("userCodeMetadata")); - - OAuth2UserCode userCode = new OAuth2UserCode(tokenValue, tokenIssuedAt, tokenExpiresAt); - builder.token(userCode, (metadata) -> metadata.putAll(userCodeMetadata)); - } - - tokenValue = rs.getString("deviceCodeValue"); - if (tokenValue != null) { - tokenIssuedAt = rs.getTimestamp("deviceCodeIssuedAt").toInstant(); - tokenExpiresAt = rs.getTimestamp("deviceCodeExpiresAt").toInstant(); - Map deviceCodeMetadata = parseMap(rs.getString("deviceCodeMetadata")); - - OAuth2UserCode deviceCode = new OAuth2UserCode(tokenValue, tokenIssuedAt, tokenExpiresAt); - builder.token(deviceCode, (metadata) -> metadata.putAll(deviceCodeMetadata)); - } - - return builder.build(); - } - - private Map parseMap(String data) { - try { - return getObjectMapper().readValue(data, new TypeReference<>() { - }); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - } - - private static final class CustomOAuth2AuthorizationParametersMapper - extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - @Override - public List apply(OAuth2Authorization authorization) { - List parameters = new ArrayList<>(); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getId())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getRegisteredClientId())); - parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getPrincipalName())); - parameters - .add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue())); - - String authorizedScopes = null; - if (!CollectionUtils.isEmpty(authorization.getAuthorizedScopes())) { - authorizedScopes = StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), - ","); - } - parameters.add(new SqlParameterValue(Types.VARCHAR, authorizedScopes)); - - String attributes = writeMap(authorization.getAttributes()); - parameters.add(new SqlParameterValue(Types.VARCHAR, attributes)); - - String state = null; - String authorizationState = authorization.getAttribute(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(authorizationState)) { - state = authorizationState; - } - parameters.add(new SqlParameterValue(Types.VARCHAR, state)); - - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - List authorizationCodeSqlParameters = toSqlParameterList(authorizationCode); - parameters.addAll(authorizationCodeSqlParameters); - - OAuth2Authorization.Token accessToken = authorization - .getToken(OAuth2AccessToken.class); - List accessTokenSqlParameters = toSqlParameterList(accessToken); - parameters.addAll(accessTokenSqlParameters); - String accessTokenType = null; - String accessTokenScopes = null; - if (accessToken != null) { - accessTokenType = accessToken.getToken().getTokenType().getValue(); - if (!CollectionUtils.isEmpty(accessToken.getToken().getScopes())) { - accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), - ","); - } - } - parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenType)); - parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes)); - - OAuth2Authorization.Token oidcIdToken = authorization.getToken(OidcIdToken.class); - List oidcIdTokenSqlParameters = toSqlParameterList(oidcIdToken); - parameters.addAll(oidcIdTokenSqlParameters); - - OAuth2Authorization.Token refreshToken = authorization.getRefreshToken(); - List refreshTokenSqlParameters = toSqlParameterList(refreshToken); - parameters.addAll(refreshTokenSqlParameters); - - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); - List userCodeSqlParameters = toSqlParameterList(userCode); - parameters.addAll(userCodeSqlParameters); - - OAuth2Authorization.Token deviceCode = authorization.getToken(OAuth2DeviceCode.class); - List deviceCodeSqlParameters = toSqlParameterList(deviceCode); - parameters.addAll(deviceCodeSqlParameters); - - return parameters; - } - - private List toSqlParameterList( - OAuth2Authorization.Token token) { - List parameters = new ArrayList<>(); - String tokenValue = null; - Timestamp tokenIssuedAt = null; - Timestamp tokenExpiresAt = null; - String metadata = null; - if (token != null) { - tokenValue = token.getToken().getTokenValue(); - if (token.getToken().getIssuedAt() != null) { - tokenIssuedAt = Timestamp.from(token.getToken().getIssuedAt()); - } - if (token.getToken().getExpiresAt() != null) { - tokenExpiresAt = Timestamp.from(token.getToken().getExpiresAt()); - } - metadata = writeMap(token.getMetadata()); - } - parameters.add(new SqlParameterValue(Types.VARCHAR, tokenValue)); - parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt)); - parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt)); - parameters.add(new SqlParameterValue(Types.VARCHAR, metadata)); - return parameters; - } - - private String writeMap(Map data) { - try { - return getObjectMapper().writeValueAsString(data); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentTests.java deleted file mode 100644 index 256ba86af..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OAuth2AuthorizationConsent}. - * - * @author Daniel Garnier-Moiroux - */ -public class OAuth2AuthorizationConsentTests { - - @Test - public void fromWhenAuthorizationConsentNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OAuth2AuthorizationConsent.from(null)) - .withMessage("authorizationConsent cannot be null"); - } - - @Test - public void withIdWhenRegisteredClientIdNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OAuth2AuthorizationConsent.withId(null, "some-user")) - .withMessage("registeredClientId cannot be empty"); - } - - @Test - public void withIdWhenPrincipalNameNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OAuth2AuthorizationConsent.withId("some-client", null)) - .withMessage("principalName cannot be empty"); - } - - @Test - public void buildWhenAuthoritiesEmptyThenThrowIllegalArgumentException() { - OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId("some-client", "some-user"); - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("authorities cannot be empty"); - } - - @Test - public void buildWhenAllAttributesAreProvidedThenAllAttributesAreSet() { - OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.withId("some-client", "some-user") - .scope("resource.read") - .scope("resource.write") - .authority(new SimpleGrantedAuthority("CLAIM_email")) - .build(); - - assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo("some-client"); - assertThat(authorizationConsent.getPrincipalName()).isEqualTo("some-user"); - assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder("resource.read", "resource.write"); - assertThat(authorizationConsent.getAuthorities()).containsExactlyInAnyOrder( - new SimpleGrantedAuthority("SCOPE_resource.read"), new SimpleGrantedAuthority("SCOPE_resource.write"), - new SimpleGrantedAuthority("CLAIM_email")); - } - - @Test - public void fromWhenAuthorizationConsentProvidedThenCopied() { - OAuth2AuthorizationConsent previousAuthorizationConsent = OAuth2AuthorizationConsent - .withId("some-client", "some-principal") - .scope("first.scope") - .scope("second.scope") - .authority(new SimpleGrantedAuthority("CLAIM_email")) - .build(); - - OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.from(previousAuthorizationConsent) - .build(); - - assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo("some-client"); - assertThat(authorizationConsent.getPrincipalName()).isEqualTo("some-principal"); - assertThat(authorizationConsent.getAuthorities()).containsExactlyInAnyOrder( - new SimpleGrantedAuthority("SCOPE_first.scope"), new SimpleGrantedAuthority("SCOPE_second.scope"), - new SimpleGrantedAuthority("CLAIM_email")); - } - - @Test - public void authoritiesThenCustomizesAuthorities() { - OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.withId("some-client", "some-user") - .authority(new SimpleGrantedAuthority("some.authority")) - .authorities((authorities) -> { - authorities.clear(); - authorities.add(new SimpleGrantedAuthority("other.authority")); - }) - .build(); - - assertThat(authorizationConsent.getAuthorities()) - .containsExactly(new SimpleGrantedAuthority("other.authority")); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java deleted file mode 100644 index b9afdeed3..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java +++ /dev/null @@ -1,640 +0,0 @@ -/* - * Copyright 2020-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.server.authorization; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata.Builder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OAuth2AuthorizationServerMetadata}. - * - * @author Daniel Garnier-Moiroux - */ -public class OAuth2AuthorizationServerMetadataTests { - - // @formatter:off - private final Builder minimalBuilder = - OAuth2AuthorizationServerMetadata.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .responseType("code"); - // @formatter:on - - @Test - public void buildWhenAllClaimsProvidedThenCreated() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .pushedAuthorizationRequestEndpoint("https://example.com/oauth2/par") - .tokenEndpoint("https://example.com/oauth2/token") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .jwkSetUrl("https://example.com/oauth2/jwks") - .scope("openid") - .responseType("code") - .grantType("authorization_code") - .grantType("client_credentials") - .tokenRevocationEndpoint("https://example.com/oauth2/revoke") - .tokenRevocationEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .tokenIntrospectionEndpoint("https://example.com/oauth2/introspect") - .tokenIntrospectionEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .codeChallengeMethod("S256") - .tlsClientCertificateBoundAccessTokens(true) - .dPoPSigningAlgorithm(JwsAlgorithms.RS256) - .dPoPSigningAlgorithm(JwsAlgorithms.ES256) - .claim("a-claim", "a-value") - .build(); - - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint()) - .isEqualTo(url("https://example.com/oauth2/par")); - assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid"); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/revoke")); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()) - .isEqualTo(url("https://example.com/oauth2/introspect")); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactly("S256"); - assertThat(authorizationServerMetadata.isTlsClientCertificateBoundAccessTokens()).isTrue(); - assertThat(authorizationServerMetadata.getDPoPSigningAlgorithms()).containsExactly(JwsAlgorithms.RS256, - JwsAlgorithms.ES256); - assertThat(authorizationServerMetadata.getClaimAsString("a-claim")).isEqualTo("a-value"); - } - - @Test - public void buildWhenOnlyRequiredClaimsProvidedThenCreated() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .responseType("code") - .build(); - - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint()).isNull(); - assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isNull(); - assertThat(authorizationServerMetadata.getScopes()).isNull(); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getGrantTypes()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull(); - assertThat(authorizationServerMetadata.getDPoPSigningAlgorithms()).isNull(); - } - - @Test - public void withClaimsWhenClaimsProvidedThenCreated() { - HashMap claims = new HashMap<>(); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "https://example.com"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, - "https://example.com/oauth2/authorize"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, - "https://example.com/oauth2/par"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/oauth2/token"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "https://example.com/oauth2/jwks"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, - Collections.singletonList("code")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, - "https://example.com/oauth2/revoke"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, - "https://example.com/oauth2/introspect"); - claims.put("some-claim", "some-value"); - - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata - .withClaims(claims) - .build(); - - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint()) - .isEqualTo(url("https://example.com/oauth2/par")); - assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid"); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getGrantTypes()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/revoke")); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()) - .isEqualTo(url("https://example.com/oauth2/introspect")); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull(); - assertThat(authorizationServerMetadata.getDPoPSigningAlgorithms()).isNull(); - assertThat(authorizationServerMetadata.getClaimAsString("some-claim")).isEqualTo("some-value"); - } - - @Test - public void withClaimsWhenClaimsWithUrlsProvidedThenCreated() { - HashMap claims = new HashMap<>(); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, url("https://example.com")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, - url("https://example.com/oauth2/authorize")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, - url("https://example.com/oauth2/par")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/oauth2/token")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, url("https://example.com/oauth2/jwks")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, - Collections.singletonList("code")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, - url("https://example.com/oauth2/revoke")); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, - url("https://example.com/oauth2/introspect")); - claims.put("some-claim", "some-value"); - - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata - .withClaims(claims) - .build(); - - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint()) - .isEqualTo(url("https://example.com/oauth2/par")); - assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(authorizationServerMetadata.getScopes()).isNull(); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getGrantTypes()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/revoke")); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()) - .isEqualTo(url("https://example.com/oauth2/introspect")); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull(); - assertThat(authorizationServerMetadata.getDPoPSigningAlgorithms()).isNull(); - assertThat(authorizationServerMetadata.getClaimAsString("some-claim")).isEqualTo("some-value"); - } - - @Test - public void withClaimsWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OAuth2AuthorizationServerMetadata.withClaims(null)) - .withMessage("claims cannot be empty"); - } - - @Test - public void withClaimsWhenMissingRequiredClaimsThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OAuth2AuthorizationServerMetadata.withClaims(Collections.emptyMap())) - .withMessage("claims cannot be empty"); - } - - @Test - public void buildWhenCalledTwiceThenGeneratesTwoConfigurations() { - OAuth2AuthorizationServerMetadata first = this.minimalBuilder.grantType("client_credentials").build(); - - OAuth2AuthorizationServerMetadata second = this.minimalBuilder.claims((claims) -> { - List newGrantTypes = new ArrayList<>(); - newGrantTypes.add("authorization_code"); - newGrantTypes.add("custom_grant"); - claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes); - }).build(); - - assertThat(first.getGrantTypes()).containsExactly("client_credentials"); - assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant"); - } - - @Test - public void buildWhenMissingIssuerThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.ISSUER)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("issuer cannot be null"); - } - - @Test - public void buildWhenIssuerNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("issuer must be a valid URL"); - } - - @Test - public void buildWhenMissingAuthorizationEndpointThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT)); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("authorizationEndpoint cannot be null"); - } - - @Test - public void buildWhenAuthorizationEndpointNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("authorizationEndpoint must be a valid URL"); - } - - @Test - public void buildWhenPushedAuthorizationRequestEndpointNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("pushedAuthorizationRequestEndpoint must be a valid URL"); - } - - @Test - public void buildWhenMissingTokenEndpointThenThrowsIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("tokenEndpoint cannot be null"); - } - - @Test - public void buildWhenTokenEndpointNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenEndpoint must be a valid URL"); - } - - @Test - public void buildWhenTokenEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("tokenEndpointAuthenticationMethods must be of type List"); - } - - @Test - public void buildWhenTokenEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims.put( - OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, - Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenEndpointAuthenticationMethods cannot be empty"); - } - - @Test - public void buildWhenTokenEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .tokenEndpointAuthenticationMethod("should-be-removed") - .tokenEndpointAuthenticationMethods((authMethods) -> { - authMethods.clear(); - authMethods.add("some-authentication-method"); - }) - .build(); - - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()) - .containsExactly("some-authentication-method"); - } - - @Test - public void buildWhenJwksUriNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("jwksUri must be a valid URL"); - } - - @Test - public void buildWhenScopesNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("scopes must be of type List"); - } - - @Test - public void buildWhenScopesEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("scopes cannot be empty"); - } - - @Test - public void buildWhenScopesAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder.scope("should-be-removed") - .scopes((scopes) -> { - scopes.clear(); - scopes.add("some-scope"); - }) - .build(); - - assertThat(authorizationServerMetadata.getScopes()).containsExactly("some-scope"); - } - - @Test - public void buildWhenMissingResponseTypesThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("responseTypes cannot be null"); - } - - @Test - public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("responseTypes must be of type List"); - } - - @Test - public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("responseTypes cannot be empty"); - } - - @Test - public void buildWhenResponseTypesAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .responseType("should-be-removed") - .responseTypes((responseTypes) -> { - responseTypes.clear(); - responseTypes.add("some-response-type"); - }) - .build(); - - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("some-response-type"); - } - - @Test - public void buildWhenResponseTypesNotPresentAndAddingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)) - .responseTypes((responseTypes) -> responseTypes.add("some-response-type")) - .build(); - - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("some-response-type"); - } - - @Test - public void buildWhenGrantTypesNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("grantTypes must be of type List"); - } - - @Test - public void buildWhenGrantTypesEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("grantTypes cannot be empty"); - } - - @Test - public void buildWhenGrantTypesAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .grantType("should-be-removed") - .grantTypes((grantTypes) -> { - grantTypes.clear(); - grantTypes.add("some-grant-type"); - }) - .build(); - - assertThat(authorizationServerMetadata.getGrantTypes()).containsExactly("some-grant-type"); - } - - @Test - public void buildWhenTokenRevocationEndpointNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.tokenRevocationEndpoint("not a valid URL"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenRevocationEndpoint must be a valid URL"); - } - - @Test - public void buildWhenTokenRevocationEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("tokenRevocationEndpointAuthenticationMethods must be of type List"); - } - - @Test - public void buildWhenTokenRevocationEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims.put( - OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, - Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenRevocationEndpointAuthenticationMethods cannot be empty"); - } - - @Test - public void buildWhenTokenRevocationEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .tokenRevocationEndpointAuthenticationMethod("should-be-removed") - .tokenRevocationEndpointAuthenticationMethods((authMethods) -> { - authMethods.clear(); - authMethods.add("some-authentication-method"); - }) - .build(); - - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()) - .containsExactly("some-authentication-method"); - } - - @Test - public void buildWhenTokenIntrospectionEndpointNotUrlThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.tokenIntrospectionEndpoint("not a valid URL"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenIntrospectionEndpoint must be a valid URL"); - } - - @Test - public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims.put( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, - "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("tokenIntrospectionEndpointAuthenticationMethods must be of type List"); - } - - @Test - public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims.put( - OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, - Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("tokenIntrospectionEndpointAuthenticationMethods cannot be empty"); - } - - @Test - public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .tokenIntrospectionEndpointAuthenticationMethod("should-be-removed") - .tokenIntrospectionEndpointAuthenticationMethods((authMethods) -> { - authMethods.clear(); - authMethods.add("some-authentication-method"); - }) - .build(); - - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()) - .containsExactly("some-authentication-method"); - } - - @Test - public void buildWhenCodeChallengeMethodsNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("codeChallengeMethods must be of type List"); - } - - @Test - public void buildWhenCodeChallengeMethodsEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder - .claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, - Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("codeChallengeMethods cannot be empty"); - } - - @Test - public void buildWhenCodeChallengeMethodsAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .codeChallengeMethod("should-be-removed") - .codeChallengeMethods((codeChallengeMethods) -> { - codeChallengeMethods.clear(); - codeChallengeMethods.add("some-authentication-method"); - }) - .build(); - - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactly("some-authentication-method"); - } - - @Test - public void buildWhenDPoPSigningAlgorithmsNotListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims((claims) -> claims - .put(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED, "not-a-list")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("dPoPSigningAlgorithms must be of type List"); - } - - @Test - public void buildWhenDPoPSigningAlgorithmsEmptyListThenThrowIllegalArgumentException() { - Builder builder = this.minimalBuilder.claims( - (claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED, - Collections.emptyList())); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("dPoPSigningAlgorithms cannot be empty"); - } - - @Test - public void buildWhenDPoPSigningAlgorithmsAddingOrRemovingThenCorrectValues() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .dPoPSigningAlgorithm(JwsAlgorithms.RS256) - .dPoPSigningAlgorithms((algs) -> { - algs.clear(); - algs.add(JwsAlgorithms.ES256); - }) - .build(); - - assertThat(authorizationServerMetadata.getDPoPSigningAlgorithms()).containsExactly(JwsAlgorithms.ES256); - } - - @Test - public void claimWhenNameNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OAuth2AuthorizationServerMetadata.builder().claim(null, "claim-value")) - .withMessage("name cannot be empty"); - } - - @Test - public void claimWhenValueNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OAuth2AuthorizationServerMetadata.builder().claim("claim-name", null)) - .withMessage("value cannot be null"); - } - - @Test - public void claimsWhenRemovingClaimThenNotPresent() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .claim("claim-name", "claim-value") - .claims((claims) -> claims.remove("claim-name")) - .build(); - assertThat(authorizationServerMetadata.hasClaim("claim-name")).isFalse(); - } - - @Test - public void claimsWhenAddingClaimThenPresent() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder - .claim("claim-name", "claim-value") - .build(); - assertThat(authorizationServerMetadata.hasClaim("claim-name")).isTrue(); - } - - private static URL url(String urlString) { - try { - return new URL(urlString); - } - catch (Exception ex) { - throw new IllegalArgumentException("urlString must be a valid URL and valid URI"); - } - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationTests.java deleted file mode 100644 index b42d62557..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2Authorization}. - * - * @author Krisztian Toth - * @author Joe Grandja - */ -public class OAuth2AuthorizationTests { - - private static final String ID = "id"; - - private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build(); - - private static final String PRINCIPAL_NAME = "principal"; - - private static final AuthorizationGrantType AUTHORIZATION_GRANT_TYPE = AuthorizationGrantType.AUTHORIZATION_CODE; - - private static final OAuth2AccessToken ACCESS_TOKEN = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - "access-token", Instant.now(), Instant.now().plusSeconds(300)); - - private static final OAuth2RefreshToken REFRESH_TOKEN = new OAuth2RefreshToken("refresh-token", Instant.now()); - - private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plus(5, ChronoUnit.MINUTES)); - - @Test - public void withRegisteredClientWhenRegisteredClientNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2Authorization.withRegisteredClient(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClient cannot be null"); - } - - @Test - public void fromWhenAuthorizationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2Authorization.from(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - } - - @Test - public void fromWhenAuthorizationProvidedThenCopied() { - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .accessToken(ACCESS_TOKEN) - .build(); - OAuth2Authorization authorizationResult = OAuth2Authorization.from(authorization).build(); - - assertThat(authorizationResult.getId()).isEqualTo(authorization.getId()); - assertThat(authorizationResult.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId()); - assertThat(authorizationResult.getPrincipalName()).isEqualTo(authorization.getPrincipalName()); - assertThat(authorizationResult.getAuthorizationGrantType()) - .isEqualTo(authorization.getAuthorizationGrantType()); - assertThat(authorizationResult.getAccessToken()).isEqualTo(authorization.getAccessToken()); - assertThat(authorizationResult.getToken(OAuth2AuthorizationCode.class)) - .isEqualTo(authorization.getToken(OAuth2AuthorizationCode.class)); - assertThat(authorizationResult.getAttributes()).isEqualTo(authorization.getAttributes()); - } - - @Test - public void buildWhenPrincipalNameNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT).build()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("principalName cannot be empty"); - } - - @Test - public void buildWhenAuthorizationGrantTypeNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT).principalName(PRINCIPAL_NAME).build()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationGrantType cannot be null"); - } - - @Test - public void attributeWhenNameNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT).attribute(null, AUTHORIZATION_CODE)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("name cannot be empty"); - } - - @Test - public void attributeWhenValueNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT).attribute("name", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("value cannot be null"); - } - - @Test - public void buildWhenAllAttributesAreProvidedThenAllAttributesAreSet() { - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) - .id(ID) - .principalName(PRINCIPAL_NAME) - .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) - .token(AUTHORIZATION_CODE) - .accessToken(ACCESS_TOKEN) - .refreshToken(REFRESH_TOKEN) - .build(); - - assertThat(authorization.getId()).isEqualTo(ID); - assertThat(authorization.getRegisteredClientId()).isEqualTo(REGISTERED_CLIENT.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(PRINCIPAL_NAME); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AUTHORIZATION_GRANT_TYPE); - assertThat(authorization.getToken(OAuth2AuthorizationCode.class).getToken()).isEqualTo(AUTHORIZATION_CODE); - assertThat(authorization.getAccessToken().getToken()).isEqualTo(ACCESS_TOKEN); - assertThat(authorization.getRefreshToken().getToken()).isEqualTo(REFRESH_TOKEN); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java deleted file mode 100644 index ee60cf8a1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.util.CollectionUtils; - -/** - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public final class TestOAuth2Authorizations { - - private TestOAuth2Authorizations() { - } - - public static OAuth2Authorization.Builder authorization() { - return authorization(TestRegisteredClients.registeredClient().build()); - } - - public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient) { - return authorization(registeredClient, Collections.emptyMap()); - } - - public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient, - Map authorizationRequestAdditionalParameters) { - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plusSeconds(120)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - Instant.now(), Instant.now().plusSeconds(300)); - return authorization(registeredClient, authorizationCode, accessToken, Collections.emptyMap(), - authorizationRequestAdditionalParameters); - } - - public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient, - OAuth2AuthorizationCode authorizationCode) { - return authorization(registeredClient, authorizationCode, null, Collections.emptyMap(), Collections.emptyMap()); - } - - public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient, - OAuth2AccessToken accessToken, Map accessTokenClaims) { - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plusSeconds(120)); - return authorization(registeredClient, authorizationCode, accessToken, accessTokenClaims, - Collections.emptyMap()); - } - - private static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient, - OAuth2AuthorizationCode authorizationCode, OAuth2AccessToken accessToken, - Map accessTokenClaims, Map authorizationRequestAdditionalParameters) { - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://provider.com/oauth2/authorize") - .clientId(registeredClient.getClientId()) - .redirectUri(registeredClient.getRedirectUris().iterator().next()) - .scopes(registeredClient.getScopes()) - .additionalParameters(authorizationRequestAdditionalParameters) - .state("state") - .build(); - OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient) - .id("id") - .principalName("principal") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizedScopes(authorizationRequest.getScopes()) - .token(authorizationCode) - .attribute(OAuth2ParameterNames.STATE, "consent-state") - .attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest) - .attribute(Principal.class.getName(), - new TestingAuthenticationToken("principal", null, "ROLE_A", "ROLE_B")); - if (accessToken != null) { - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(), - Instant.now().plus(1, ChronoUnit.HOURS)); - builder - .token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(registeredClient, accessTokenClaims))) - .refreshToken(refreshToken); - } - - return builder; - } - - private static Map tokenMetadata(RegisteredClient registeredClient, - Map tokenClaims) { - Map tokenMetadata = new HashMap<>(); - OAuth2TokenFormat accessTokenFormat = registeredClient.getTokenSettings().getAccessTokenFormat(); - tokenMetadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue()); - tokenMetadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false); - if (CollectionUtils.isEmpty(tokenClaims)) { - tokenClaims = defaultTokenClaims(); - } - tokenMetadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, tokenClaims); - return tokenMetadata; - } - - private static Map defaultTokenClaims() { - Map claims = new HashMap<>(); - claims.put("claim1", "value1"); - claims.put("claim2", "value2"); - claims.put("claim3", "value3"); - return claims; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessorTests.java deleted file mode 100644 index 195b8741c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessorTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.aot.hint; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2AuthorizationServerBeanRegistrationAotProcessor}. - * - * @author William Koch - */ -class OAuth2AuthorizationServerBeanRegistrationAotProcessorTests { - - private OAuth2AuthorizationServerBeanRegistrationAotProcessor processor; - - private DefaultListableBeanFactory defaultListableBeanFactory; - - @BeforeEach - void setUp() { - this.processor = new OAuth2AuthorizationServerBeanRegistrationAotProcessor(); - this.defaultListableBeanFactory = new DefaultListableBeanFactory(); - - } - - @ParameterizedTest - @ValueSource(classes = { JdbcOAuth2AuthorizationService.class, CustomJdbcOAuth2AuthorizationService.class, - JdbcRegisteredClientRepository.class, CustomJdbcRegisteredClientRepository.class }) - void processAheadOfTimeWhenBeanTypeJdbcBasedImplThenReturnContribution(Class beanClass) { - this.defaultListableBeanFactory.registerBeanDefinition("beanName", new RootBeanDefinition(beanClass)); - - BeanRegistrationAotContribution aotContribution = this.processor - .processAheadOfTime(RegisteredBean.of(this.defaultListableBeanFactory, "beanName")); - - assertThat(aotContribution).isNotNull(); - } - - @ParameterizedTest - @ValueSource(classes = { InMemoryOAuth2AuthorizationService.class, InMemoryRegisteredClientRepository.class, - Object.class }) - void processAheadOfTimeWhenBeanTypeNotJdbcBasedImplThenDoesNotReturnContribution(Class beanClass) { - this.defaultListableBeanFactory.registerBeanDefinition("beanName", new RootBeanDefinition(beanClass)); - - BeanRegistrationAotContribution aotContribution = this.processor - .processAheadOfTime(RegisteredBean.of(this.defaultListableBeanFactory, "beanName")); - - assertThat(aotContribution).isNull(); - } - - @Test - void processAheadOfTimeWhenMultipleBeanTypeJdbcBasedImplThenReturnContributionOnce() { - this.defaultListableBeanFactory.registerBeanDefinition("oauth2AuthorizationService", - new RootBeanDefinition(JdbcOAuth2AuthorizationService.class)); - - this.defaultListableBeanFactory.registerBeanDefinition("registeredClientRepository", - new RootBeanDefinition(CustomJdbcRegisteredClientRepository.class)); - - BeanRegistrationAotContribution firstAotContribution = this.processor - .processAheadOfTime(RegisteredBean.of(this.defaultListableBeanFactory, "oauth2AuthorizationService")); - - BeanRegistrationAotContribution secondAotContribution = this.processor - .processAheadOfTime(RegisteredBean.of(this.defaultListableBeanFactory, "registeredClientRepository")); - - assertThat(firstAotContribution).isNotNull(); - assertThat(secondAotContribution).isNull(); - } - - static class CustomJdbcOAuth2AuthorizationService extends JdbcOAuth2AuthorizationService { - - CustomJdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - super(jdbcOperations, registeredClientRepository); - } - - } - - static class CustomJdbcRegisteredClientRepository extends JdbcRegisteredClientRepository { - - CustomJdbcRegisteredClientRepository(JdbcOperations jdbcOperations) { - super(jdbcOperations); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProviderTests.java deleted file mode 100644 index 4d41cf99f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProviderTests.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link ClientSecretAuthenticationProvider}. - * - * @author Patryk Kostrzewa - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class ClientSecretAuthenticationProviderTests { - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORIZATION_CODE = "code"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private ClientSecretAuthenticationProvider authenticationProvider; - - private PasswordEncoder passwordEncoder; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new ClientSecretAuthenticationProvider(this.registeredClientRepository, - this.authorizationService); - this.passwordEncoder = spy(new PasswordEncoder() { - @Override - public String encode(CharSequence rawPassword) { - return NoOpPasswordEncoder.getInstance().encode(rawPassword); - } - - @Override - public boolean matches(CharSequence rawPassword, String encodedPassword) { - return NoOpPasswordEncoder.getInstance().matches(rawPassword, encodedPassword); - } - - @Override - public boolean upgradeEncoding(String encodedPassword) { - return true; - } - }); - this.authenticationProvider.setPasswordEncoder(this.passwordEncoder); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new ClientSecretAuthenticationProvider(null, this.authorizationService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new ClientSecretAuthenticationProvider(this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("passwordEncoder cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId() + "-invalid", ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID); - }); - } - - @Test - public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_POST, - registeredClient.getClientSecret(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("authentication_method"); - }); - } - - @Test - public void authenticateWhenClientSecretNotProvidedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("credentials"); - }); - } - - @Test - public void authenticateWhenInvalidClientSecretThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret() + "-invalid", null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_SECRET); - }); - verify(this.passwordEncoder).matches(any(), any()); - } - - @Test - public void authenticateWhenExpiredClientSecretThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecretExpiresAt(Instant.now().minus(1, ChronoUnit.HOURS).truncatedTo(ChronoUnit.SECONDS)) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("client_secret_expires_at"); - }); - verify(this.passwordEncoder).matches(any(), any()); - } - - @Test - public void authenticateWhenValidCredentialsThenAuthenticated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.passwordEncoder).matches(any(), any()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret()); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - } - - @Test - public void authenticateWhenValidCredentialsAndRequiresUpgradingThenClientSecretUpgraded() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.passwordEncoder).matches(any(), any()); - verify(this.passwordEncoder).upgradeEncoding(any()); - verify(this.passwordEncoder).encode(any()); - verify(this.registeredClientRepository).save(any()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret()); - assertThat(authenticationResult.getRegisteredClient()).isNotSameAs(registeredClient); - } - - @Test - public void authenticateWhenAuthorizationCodeGrantAndValidCredentialsThenAuthenticated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(TestOAuth2Authorizations.authorization().build()); - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), createAuthorizationCodeTokenParameters()); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.passwordEncoder).matches(any(), any()); - verify(this.authorizationService).findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret()); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - } - - @Test - public void authenticateWhenPkceAndInvalidCodeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - parameters.put(OAuth2ParameterNames.CODE, "invalid-code"); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CODE); - }); - } - - @Test - public void authenticateWhenPkceAndMissingCodeVerifierThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createAuthorizationCodeTokenParameters(); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(PkceParameterNames.CODE_VERIFIER); - }); - } - - @Test - public void authenticateWhenPkceAndValidCodeVerifierThenAuthenticated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), parameters); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.passwordEncoder).matches(any(), any()); - verify(this.authorizationService).findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret()); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - } - - private static Map createAuthorizationCodeTokenParameters() { - Map parameters = new HashMap<>(); - parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.put(OAuth2ParameterNames.CODE, AUTHORIZATION_CODE); - return parameters; - } - - private static Map createPkceTokenParameters(String codeVerifier) { - Map parameters = createAuthorizationCodeTokenParameters(); - parameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); - return parameters; - } - - private static Map createPkceAuthorizationParametersS256() { - Map parameters = new HashMap<>(); - parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - parameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - return parameters; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java deleted file mode 100644 index b9cb6c66e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProviderTests.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jwt.BadJwtException; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.JwtValidationException; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link JwtClientAssertionAuthenticationProvider}. - * - * @author Rafal Lewczuk - * @author Joe Grandja - */ -public class JwtClientAssertionAuthenticationProviderTests { - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORIZATION_CODE = "code"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final ClientAuthenticationMethod JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD = new ClientAuthenticationMethod( - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private JwtClientAssertionAuthenticationProvider authenticationProvider; - - private AuthorizationServerSettings authorizationServerSettings; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new JwtClientAssertionAuthenticationProvider(this.registeredClientRepository, - this.authorizationService); - this.authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://auth-server.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null)); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new JwtClientAssertionAuthenticationProvider(null, this.authorizationService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new JwtClientAssertionAuthenticationProvider(this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void setJwtDecoderFactoryWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setJwtDecoderFactory(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwtDecoderFactory cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId() + "-invalid", JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, - "jwt-assertion", null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID); - }); - } - - @Test - public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("authentication_method"); - }); - } - - @Test - public void authenticateWhenCredentialsNotProvidedThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("credentials"); - }); - } - - @Test - public void authenticateWhenInvalidCredentialsThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "invalid-jwt-assertion", - null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .hasCauseInstanceOf(BadJwtException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ASSERTION); - }); - } - - @Test - public void authenticateWhenInvalidClaimsThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - // @formatter:off - JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256) - .build(); - JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder() - .issuer("invalid-iss") - .subject("invalid-sub") - .audience(Collections.singletonList("invalid-aud")) - .build(); - // @formatter:on - - JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256"); - Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, - jwtAssertion.getTokenValue(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .hasCauseInstanceOf(JwtValidationException.class) - .extracting((ex) -> (OAuth2AuthenticationException) ex) - .satisfies((ex) -> { - assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(ex.getError().getDescription()).contains(OAuth2ParameterNames.CLIENT_ASSERTION); - JwtValidationException jwtValidationException = (JwtValidationException) ex.getCause(); - assertThat(jwtValidationException.getErrors()).hasSize(4); // iss, sub, - // aud, exp - }); - } - - @Test - public void authenticateWhenValidCredentialsThenAuthenticated() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - // @formatter:off - JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256) - .build(); - JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(registeredClient) - .build(); - // @formatter:on - - JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256"); - Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, - jwtAssertion.getTokenValue(), null); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()).isInstanceOf(Jwt.class); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT); - } - - @Test - public void authenticateWhenPkceAndValidCodeVerifierThenAuthenticated() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - // @formatter:off - JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256) - .build(); - JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(registeredClient) - .build(); - // @formatter:on - - JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256"); - Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, - jwtAssertion.getTokenValue(), parameters); - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()).isInstanceOf(Jwt.class); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT); - } - - private JwtClaimsSet.Builder jwtClientAssertionClaims(RegisteredClient registeredClient) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return JwtClaimsSet.builder() - .issuer(registeredClient.getClientId()) - .subject(registeredClient.getClientId()) - .audience(Collections.singletonList(asUrl(this.authorizationServerSettings.getIssuer(), - this.authorizationServerSettings.getTokenEndpoint()))) - .issuedAt(issuedAt) - .expiresAt(expiresAt); - } - - private static JwtEncoder createEncoder(String secret, String algorithm) { - SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), algorithm); - OctetSequenceKey secretKeyJwk = TestJwks.jwk(secretKey).build(); - JWKSource jwkSource = (jwkSelector, securityContext) -> jwkSelector - .select(new JWKSet(secretKeyJwk)); - return new NimbusJwtEncoder(jwkSource); - } - - private static String asUrl(String uri, String path) { - return UriComponentsBuilder.fromUriString(uri).path(path).build().toUriString(); - } - - private static Map createAuthorizationCodeTokenParameters() { - Map parameters = new HashMap<>(); - parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.put(OAuth2ParameterNames.CODE, AUTHORIZATION_CODE); - return parameters; - } - - private static Map createPkceTokenParameters(String codeVerifier) { - Map parameters = createAuthorizationCodeTokenParameters(); - parameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); - return parameters; - } - - private static Map createPkceAuthorizationParametersS256() { - Map parameters = new HashMap<>(); - parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - parameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - return parameters; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java deleted file mode 100644 index 8c543f468..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionDecoderFactoryTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link JwtClientAssertionDecoderFactory}. - * - * @author Joe Grandja - */ -public class JwtClientAssertionDecoderFactoryTests { - - private JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory(); - - @Test - public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.jwtDecoderFactory.setJwtValidatorFactory(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwtValidatorFactory cannot be null"); - } - - @Test - public void createDecoderWhenMissingJwkSetUrlThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256) - .build() - ) - .build(); - // @formatter:on - - assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" - + registeredClient.getId() + "'. Check to ensure you have configured the JWK Set URL."); - }); - } - - @Test - public void createDecoderWhenMissingClientSecretThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSecret(null) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientSettings( - ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build() - ) - .build(); - // @formatter:on - - assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" - + registeredClient.getId() + "'. Check to ensure you have configured the client secret."); - }); - } - - @Test - public void createDecoderWhenMissingSigningAlgorithmThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .build(); - // @formatter:on - - assertThatThrownBy(() -> this.jwtDecoderFactory.createDecoder(registeredClient)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()) - .isEqualTo("Failed to find a Signature Verifier for Client: '" + registeredClient.getId() - + "'. Check to ensure you have configured a valid JWS Algorithm: 'null'."); - }); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContextTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContextTests.java deleted file mode 100644 index 6ccacc3b5..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContextTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AccessTokenAuthenticationContext} - * - * @author Dmitriy Dubson - */ -public class OAuth2AccessTokenAuthenticationContextTests { - - private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient) - .build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private final OAuth2AccessTokenAuthenticationToken accessTokenAuthenticationToken = new OAuth2AccessTokenAuthenticationToken( - this.registeredClient, this.clientPrincipal, this.authorization.getAccessToken().getToken(), - this.authorization.getRefreshToken().getToken()); - - @Test - public void withWhenAuthenticationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2AccessTokenAuthenticationContext.with(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authentication cannot be null"); - } - - @Test - public void setWhenValueNullThenThrowIllegalArgumentException() { - OAuth2AccessTokenAuthenticationContext.Builder builder = OAuth2AccessTokenAuthenticationContext - .with(this.accessTokenAuthenticationToken); - - assertThatThrownBy(() -> builder.accessTokenResponse(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("value cannot be null"); - } - - @Test - public void buildWhenAllValuesProvidedThenAllValuesAreSet() { - OAuth2AccessTokenResponse.Builder accessTokenResponseBuilder = OAuth2AccessTokenResponse - .withToken(this.accessTokenAuthenticationToken.getAccessToken().getTokenValue()); - OAuth2AccessTokenAuthenticationContext context = OAuth2AccessTokenAuthenticationContext - .with(this.accessTokenAuthenticationToken) - .accessTokenResponse(accessTokenResponseBuilder) - .build(); - - assertThat(context.getAuthentication()).isEqualTo(this.accessTokenAuthenticationToken); - assertThat(context.getAccessTokenResponse()).isEqualTo(accessTokenResponseBuilder); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationTokenTests.java deleted file mode 100644 index f95a55d3a..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationTokenTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AccessTokenAuthenticationToken}. - * - * @author Joe Grandja - */ -public class OAuth2AccessTokenAuthenticationTokenTests { - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - Instant.now(), Instant.now().plusSeconds(300)); - - private OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(), - Instant.now().plus(1, ChronoUnit.DAYS)); - - private Map additionalParameters = Collections.singletonMap("custom-param", "custom-value"); - - @Test - public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AccessTokenAuthenticationToken(null, this.clientPrincipal, this.accessToken)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClient cannot be null"); - } - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2AccessTokenAuthenticationToken(this.registeredClient, null, this.accessToken)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenAccessTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2AccessTokenAuthenticationToken(this.registeredClient, this.clientPrincipal, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("accessToken cannot be null"); - } - - @Test - public void constructorWhenAdditionalParametersNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AccessTokenAuthenticationToken(this.registeredClient, this.clientPrincipal, - this.accessToken, this.refreshToken, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("additionalParameters cannot be null"); - } - - @Test - public void constructorWhenAllValuesProvidedThenCreated() { - OAuth2AccessTokenAuthenticationToken authentication = new OAuth2AccessTokenAuthenticationToken( - this.registeredClient, this.clientPrincipal, this.accessToken, this.refreshToken, - this.additionalParameters); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getRegisteredClient()).isEqualTo(this.registeredClient); - assertThat(authentication.getAccessToken()).isEqualTo(this.accessToken); - assertThat(authentication.getRefreshToken()).isEqualTo(this.refreshToken); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java deleted file mode 100644 index ed13047e4..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java +++ /dev/null @@ -1,838 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2AuthorizationCodeAuthenticationProvider}. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class OAuth2AuthorizationCodeAuthenticationProviderTests { - - private static final String AUTHORIZATION_CODE = "code"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private OAuth2AuthorizationService authorizationService; - - private JwtEncoder jwtEncoder; - - private OAuth2TokenCustomizer jwtCustomizer; - - private OAuth2TokenCustomizer accessTokenCustomizer; - - private OAuth2TokenGenerator tokenGenerator; - - private JwtEncoder dPoPProofJwtEncoder; - - private SessionRegistry sessionRegistry; - - private OAuth2AuthorizationCodeAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.jwtEncoder = mock(JwtEncoder.class); - this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); - JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder); - jwtGenerator.setJwtCustomizer(this.jwtCustomizer); - this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); - accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, - accessTokenGenerator, refreshTokenGenerator); - this.tokenGenerator = spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - this.dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - this.sessionRegistry = mock(SessionRegistry.class); - this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(this.authorizationService, - this.tokenGenerator); - this.authenticationProvider.setSessionRegistry(this.sessionRegistry); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationProvider(null, this.tokenGenerator)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationProvider(this.authorizationService, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenGenerator cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2AuthorizationCodeAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isTrue(); - } - - @Test - public void setSessionRegistryWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setSessionRegistry(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("sessionRegistry cannot be null"); - } - - @Test - public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(registeredClient.getClientId(), - registeredClient.getClientSecret()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenInvalidCodeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenCodeIssuedToAnotherClientThenThrowOAuth2AuthenticationException() { - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - OAuth2Authorization.Token authorizationCode = updatedAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCode.isInvalidated()).isTrue(); - } - - @Test - public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri() + "-invalid", null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenInvalidatedCodeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(AUTHORIZATION_CODE, Instant.now(), - Instant.now().plusSeconds(120)); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(authorizationCode, - (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getAccessToken().isInvalidated()).isTrue(); - assertThat(updatedAuthorization.getRefreshToken().isInvalidated()).isTrue(); - } - - // gh-1233 - @Test - public void authenticateWhenInvalidatedCodeAndAccessTokenNullThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(AUTHORIZATION_CODE, Instant.now(), - Instant.now().plusSeconds(120)); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient, authorizationCode) - .token(authorizationCode, - (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - - verify(this.authorizationService, never()).save(any()); - } - - // gh-290 - @Test - public void authenticateWhenExpiredCodeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(AUTHORIZATION_CODE, - Instant.now().minusSeconds(300), Instant.now().minusSeconds(60)); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(authorizationCode) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenAccessTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - return null; - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()).contains("The token generator failed to generate the access token."); - }); - } - - @Test - public void authenticateWhenInvalidRefreshTokenGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), - Instant.now().plusSeconds(300)); - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()) - .contains("The token generator failed to generate a valid refresh token."); - }); - } - - @Test - public void authenticateWhenIdTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - return null; - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()).contains("The token generator failed to generate the ID token."); - }); - } - - @Test - public void authenticateWhenValidCodeThenReturnAccessToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Map additionalParameters = new HashMap<>(); - additionalParameters.put("dpop_proof", generateDPoPProof("http://localhost/oauth2/token")); - additionalParameters.put("dpop_method", "POST"); - additionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), additionalParameters); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(jwtEncodingContext.getPrincipal()) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(authorization); - assertThat(jwtEncodingContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(jwtEncodingContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(jwtEncodingContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(jwtEncodingContext.getJwsHeader()).isNotNull(); - assertThat(jwtEncodingContext.getClaims()).isNotNull(); - assertThat(jwtEncodingContext.get(OAuth2TokenContext.DPOP_PROOF_KEY)).isNotNull(); - - ArgumentCaptor jwtEncoderParametersCaptor = ArgumentCaptor - .forClass(JwtEncoderParameters.class); - verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture()); - JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims(); - - Set scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE); - assertThat(scopes).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(jwtClaimsSet.getSubject()).isEqualTo(authorization.getPrincipalName()); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRegisteredClient().getId()) - .isEqualTo(updatedAuthorization.getRegisteredClientId()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - assertThat(accessTokenAuthentication.getAccessToken().getScopes()) - .isEqualTo(authorization.getAuthorizedScopes()); - assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull(); - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - OAuth2Authorization.Token authorizationCode = updatedAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCode.isInvalidated()).isTrue(); - } - - @Test - public void authenticateWhenValidCodeAndAuthenticationRequestThenReturnIdToken() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plusSeconds(120)); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient, authorizationCode) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - Authentication principal = authorization.getAttribute(Principal.class.getName()); - - List sessions = new ArrayList<>(); - sessions.add(new SessionInformation(principal.getPrincipal(), "session3", Date.from(Instant.now()))); - sessions.add(new SessionInformation(principal.getPrincipal(), "session2", - Date.from(Instant.now().minus(1, ChronoUnit.HOURS)))); - sessions.add(new SessionInformation(principal.getPrincipal(), "session1", - Date.from(Instant.now().minus(2, ChronoUnit.HOURS)))); - SessionInformation expectedSession = sessions.get(0); // Most recent - given(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(false))).willReturn(sessions); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer, times(2)).customize(jwtEncodingContextCaptor.capture()); - // Access Token context - JwtEncodingContext accessTokenContext = jwtEncodingContextCaptor.getAllValues().get(0); - assertThat(accessTokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(accessTokenContext.getPrincipal()).isEqualTo(principal); - assertThat(accessTokenContext.getAuthorization()).isEqualTo(authorization); - assertThat(accessTokenContext.getAuthorization().getAccessToken()).isNull(); - assertThat(accessTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(accessTokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(accessTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(accessTokenContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(accessTokenContext.getJwsHeader()).isNotNull(); - assertThat(accessTokenContext.getClaims()).isNotNull(); - Map claims = new HashMap<>(); - accessTokenContext.getClaims().claims(claims::putAll); - assertThat(claims).flatExtracting(OAuth2ParameterNames.SCOPE) - .containsExactlyInAnyOrder(OidcScopes.OPENID, "scope1"); - // ID Token context - JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1); - assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(idTokenContext.getPrincipal()).isEqualTo(principal); - assertThat(idTokenContext.getAuthorization()).isNotEqualTo(authorization); - assertThat(idTokenContext.getAuthorization().getAccessToken()).isNotNull(); - assertThat(idTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN); - assertThat(idTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(idTokenContext.getAuthorizationGrant()) - .isEqualTo(authentication); - SessionInformation sessionInformation = idTokenContext.get(SessionInformation.class); - assertThat(sessionInformation).isNotNull(); - assertThat(sessionInformation.getSessionId()).isEqualTo(createHash(expectedSession.getSessionId())); - assertThat(idTokenContext.getJwsHeader()).isNotNull(); - assertThat(idTokenContext.getClaims()).isNotNull(); - - verify(this.jwtEncoder, times(2)).encode(any()); // Access token and ID Token - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRegisteredClient().getId()) - .isEqualTo(updatedAuthorization.getRegisteredClientId()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - Set accessTokenScopes = new HashSet<>(updatedAuthorization.getAuthorizedScopes()); - assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(accessTokenScopes); - assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull(); - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - OAuth2Authorization.Token authorizationCodeToken = updatedAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token idToken = updatedAuthorization.getToken(OidcIdToken.class); - assertThat(idToken).isNotNull(); - assertThat(accessTokenAuthentication.getAdditionalParameters()) - .containsExactly(entry(OidcParameterNames.ID_TOKEN, idToken.getToken().getTokenValue())); - } - - // gh-296 - @Test - public void authenticateWhenPublicClientThenRefreshTokenNotIssued() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.NONE, null); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(jwtEncodingContext.getPrincipal()) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(authorization); - assertThat(jwtEncodingContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(jwtEncodingContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(jwtEncodingContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(jwtEncodingContext.getJwsHeader()).isNotNull(); - assertThat(jwtEncodingContext.getClaims()).isNotNull(); - - ArgumentCaptor jwtEncoderParametersCaptor = ArgumentCaptor - .forClass(JwtEncoderParameters.class); - verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture()); - JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims(); - - Set scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE); - assertThat(scopes).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(jwtClaimsSet.getSubject()).isEqualTo(authorization.getPrincipalName()); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRegisteredClient().getId()) - .isEqualTo(updatedAuthorization.getRegisteredClientId()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - assertThat(accessTokenAuthentication.getAccessToken().getScopes()) - .isEqualTo(authorization.getAuthorizedScopes()); - assertThat(accessTokenAuthentication.getRefreshToken()).isNull(); - OAuth2Authorization.Token authorizationCode = updatedAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCode.isInvalidated()).isTrue(); - } - - @Test - public void authenticateWhenTokenTimeToLiveConfiguredThenTokenExpirySet() { - Duration accessTokenTTL = Duration.ofHours(2); - Duration refreshTokenTTL = Duration.ofDays(1); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(TokenSettings.builder() - .accessTokenTimeToLive(accessTokenTTL) - .refreshTokenTimeToLive(refreshTokenTTL) - .build()) - .build(); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - Instant accessTokenIssuedAt = Instant.now(); - Instant accessTokenExpiresAt = accessTokenIssuedAt.plus(accessTokenTTL); - given(this.jwtEncoder.encode(any())).willReturn(createJwt(accessTokenIssuedAt, accessTokenExpiresAt)); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - Instant expectedAccessTokenExpiresAt = accessTokenAuthentication.getAccessToken() - .getIssuedAt() - .plus(accessTokenTTL); - assertThat(accessTokenAuthentication.getAccessToken().getExpiresAt()) - .isBetween(expectedAccessTokenExpiresAt.minusSeconds(1), expectedAccessTokenExpiresAt.plusSeconds(1)); - - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - Instant expectedRefreshTokenExpiresAt = accessTokenAuthentication.getRefreshToken() - .getIssuedAt() - .plus(refreshTokenTTL); - assertThat(accessTokenAuthentication.getRefreshToken().getExpiresAt()) - .isBetween(expectedRefreshTokenExpiresAt.minusSeconds(1), expectedRefreshTokenExpiresAt.plusSeconds(1)); - } - - @Test - public void authenticateWhenRefreshTokenGrantNotConfiguredThenRefreshTokenNotIssued() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantTypes((grantTypes) -> grantTypes.remove(AuthorizationGrantType.REFRESH_TOKEN)) - .build(); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt()); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(accessTokenAuthentication.getRefreshToken()).isNull(); - } - - @Test - public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build()) - .build(); - // @formatter:on - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null); - - this.authenticationProvider.authenticate(authentication); - - verify(this.accessTokenCustomizer).customize(any()); - } - - private static Jwt createJwt() { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return createJwt(issuedAt, expiresAt); - } - - private static Jwt createJwt(Instant issuedAt, Instant expiresAt) { - return Jwt.withTokenValue("token") - .header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName()) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .build(); - } - - private static String createHash(String value) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - private String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = this.dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java deleted file mode 100644 index 69bfe5d1e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AuthorizationCodeAuthenticationToken}. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class OAuth2AuthorizationCodeAuthenticationTokenTests { - - private String code = "code"; - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private String redirectUri = "redirectUri"; - - private Map additionalParameters = Collections.singletonMap("param1", "value1"); - - @Test - public void constructorWhenCodeNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.clientPrincipal, - this.redirectUri, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("code cannot be empty"); - } - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2AuthorizationCodeAuthenticationToken(this.code, null, this.redirectUri, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenClientPrincipalProvidedThenCreated() { - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters); - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getCode()).isEqualTo(this.code); - assertThat(authentication.getRedirectUri()).isEqualTo(this.redirectUri); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - - @Test - public void getAdditionalParametersWhenUpdateThenThrowUnsupportedOperationException() { - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters); - assertThatThrownBy(() -> authentication.getAdditionalParameters().put("another_key", 1)) - .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy(() -> authentication.getAdditionalParameters().remove("some_key")) - .isInstanceOf(UnsupportedOperationException.class); - assertThatThrownBy(() -> authentication.getAdditionalParameters().clear()) - .isInstanceOf(UnsupportedOperationException.class); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java deleted file mode 100644 index 21bf3fe00..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java +++ /dev/null @@ -1,817 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2AuthorizationCodeRequestAuthenticationProvider}. - * - * @author Joe Grandja - * @author Steve Riesenberg - */ -public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize"; - - private static final String STATE = "state"; - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2AuthorizationCodeRequestAuthenticationProvider authenticationProvider; - - private TestingAuthenticationToken principal; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class); - this.authenticationProvider = new OAuth2AuthorizationCodeRequestAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, this.authorizationConsentService); - this.principal = new TestingAuthenticationToken("principalName", "password"); - this.principal.setAuthenticated(true); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeRequestAuthenticationProvider(null, - this.authorizationService, this.authorizationConsentService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2AuthorizationCodeRequestAuthenticationProvider(this.registeredClientRepository, null, - this.authorizationConsentService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenAuthorizationConsentServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2AuthorizationCodeRequestAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2AuthorizationCodeRequestAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeRequestAuthenticationToken.class)) - .isTrue(); - } - - @Test - public void setAuthorizationCodeGeneratorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationCodeGenerator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationCodeGenerator cannot be null"); - } - - @Test - public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationValidator cannot be null"); - } - - @Test - public void setAuthorizationConsentRequiredWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentRequired(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentRequired cannot be null"); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)); - } - - // gh-243 - @Test - public void authenticateWhenInvalidRedirectUriHostThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, "https:///invalid", STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - // gh-243 - @Test - public void authenticateWhenInvalidRedirectUriFragmentThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, "https://example.com#fragment", - STATE, registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenUnregisteredRedirectUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, "https://invalid-example.com", STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - // gh-243 - @Test - public void authenticateWhenRedirectUriIPv4LoopbackAndDifferentPortThenReturnAuthorizationCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://127.0.0.1:8080") - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, "https://127.0.0.1:5000", STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - } - - // gh-243 - @Test - public void authenticateWhenRedirectUriIPv6LoopbackAndDifferentPortThenReturnAuthorizationCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://[::1]:8080") - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, "https://[::1]:5000", STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - } - - @Test - public void authenticateWhenMissingRedirectUriAndMultipleRegisteredThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://example2.com") - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, null, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenAuthenticationRequestMissingRedirectUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - // redirect_uri is REQUIRED for OpenID Connect requests - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, null, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenClientNotAuthorizedToRequestCodeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantTypes(Set::clear) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenInvalidScopeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - Collections.singleton("invalid-scope"), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPkceRequiredAndMissingCodeChallengeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireProofKey(true).build()) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPkceUnsupportedCodeChallengeMethodThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported"); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, - authentication.getRedirectUri())); - } - - // gh-770 - @Test - public void authenticateWhenPkceMissingCodeChallengeMethodThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneLoginThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none login"); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneConsentThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none consent"); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneSelectAccountThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none select_account"); - } - - private void assertWhenAuthenticationRequestWithPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - String prompt) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put("prompt", prompt); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, "prompt", authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedAndPromptNoneThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - this.principal.setAuthenticated(false); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put("prompt", "none"); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - "login_required", "prompt", authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - this.principal.setAuthenticated(false); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(authenticationResult).isSameAs(authentication); - assertThat(authenticationResult.isAuthenticated()).isFalse(); - } - - @Test - public void authenticateWhenRequireAuthorizationConsentAndPromptNoneThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(OidcScopes.OPENID) - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put("prompt", "none"); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - "consent_required", "prompt", authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenRequireAuthorizationConsentThenReturnAuthorizationConsent() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationConsentAuthenticationToken authenticationResult = (OAuth2AuthorizationConsentAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization authorization = authorizationCaptor.getValue(); - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE); - assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(authentication.getAuthorizationUri()); - assertThat(authorizationRequest.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authorizationRequest.getRedirectUri()).isEqualTo(authentication.getRedirectUri()); - assertThat(authorizationRequest.getScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorizationRequest.getState()).isEqualTo(authentication.getState()); - assertThat(authorizationRequest.getAdditionalParameters()).isEqualTo(authentication.getAdditionalParameters()); - - assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorization.getAttribute(Principal.class.getName())).isEqualTo(this.principal); - String state = authorization.getAttribute(OAuth2ParameterNames.STATE); - assertThat(state).isNotNull(); - assertThat(state).isNotEqualTo(authentication.getState()); - - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri()); - assertThat(authenticationResult.getScopes()).isEmpty(); - assertThat(authenticationResult.getState()).isEqualTo(state); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - } - - @Test - public void authenticateWhenRequireAuthorizationConsentAndOnlyOpenidScopeRequestedThenAuthorizationConsentNotRequired() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .scopes((scopes) -> { - scopes.clear(); - scopes.add(OidcScopes.OPENID); - }) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - } - - @Test - public void authenticateWhenRequireAuthorizationConsentAndAllPreviouslyApprovedThenAuthorizationConsentNotRequired() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClient.getId(), - this.principal.getName()); - registeredClient.getScopes().forEach(builder::scope); - OAuth2AuthorizationConsent previousAuthorizationConsent = builder.build(); - given(this.authorizationConsentService.findById(eq(registeredClient.getId()), eq(this.principal.getName()))) - .willReturn(previousAuthorizationConsent); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - } - - @Test - public void authenticateWhenCustomAuthorizationConsentRequiredThenUsed() { - @SuppressWarnings("unchecked") - Predicate authorizationConsentRequired = mock( - Predicate.class); - this.authenticationProvider.setAuthorizationConsentRequired(authorizationConsentRequired); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - - verify(authorizationConsentRequired).test(any()); - } - - @Test - public void authenticateWhenAuthorizationCodeRequestValidThenReturnAuthorizationCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - } - - @Test - public void authenticateWhenAuthorizationCodeRequestWithRequestUriThenReturnAuthorizationCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri - .create(); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.REQUEST_URI, pushedAuthorizationRequestUri.getRequestUri()); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, additionalParameters) - .build(); - given(this.authorizationService.findByToken(eq(pushedAuthorizationRequestUri.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, null, null, null, - additionalParameters); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - verify(this.authorizationService).remove(eq(authorization)); - } - - @Test - public void authenticateWhenAuthorizationCodeRequestWithInvalidRequestUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri - .create(); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.REQUEST_URI, pushedAuthorizationRequestUri.getRequestUri()); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, additionalParameters) - .build(); - given(this.authorizationService.findByToken(eq(pushedAuthorizationRequestUri.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, null, null, null, - Collections.singletonMap(OAuth2ParameterNames.REQUEST_URI, "invalid_request_uri")); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, null)); - } - - @Test - public void authenticateWhenAuthorizationCodeRequestWithRequestUriIssuedToAnotherClientThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient anotherRegisteredClient = TestRegisteredClients.registeredClient2().build(); - - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri - .create(); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.REQUEST_URI, pushedAuthorizationRequestUri.getRequestUri()); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, additionalParameters) - .build(); - given(this.authorizationService.findByToken(eq(pushedAuthorizationRequestUri.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, anotherRegisteredClient.getClientId(), this.principal, null, null, null, - additionalParameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)); - } - - @Test - public void authenticateWhenAuthorizationCodeRequestWithExpiredRequestUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri - .create(Instant.now().minusSeconds(5)); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.REQUEST_URI, pushedAuthorizationRequestUri.getRequestUri()); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, additionalParameters) - .build(); - given(this.authorizationService.findByToken(eq(pushedAuthorizationRequestUri.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, null, null, null, - additionalParameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, null)); - verify(this.authorizationService).remove(eq(authorization)); - } - - @Test - public void authenticateWhenAuthorizationCodeNotGeneratedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - @SuppressWarnings("unchecked") - OAuth2TokenGenerator authorizationCodeGenerator = mock(OAuth2TokenGenerator.class); - this.authenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthorizationCodeRequestAuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()) - .contains("The token generator failed to generate the authorization code."); - }); - } - - @Test - public void authenticateWhenCustomAuthenticationValidatorThenUsed() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - @SuppressWarnings("unchecked") - Consumer authenticationValidator = mock(Consumer.class); - this.authenticationProvider.setAuthenticationValidator(authenticationValidator); - - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, - registeredClient.getScopes(), null); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, - authenticationResult); - - verify(authenticationValidator).accept(any()); - } - - private void assertAuthorizationCodeRequestWithAuthorizationCodeResult(RegisteredClient registeredClient, - OAuth2AuthorizationCodeRequestAuthenticationToken authentication, - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) { - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization authorization = authorizationCaptor.getValue(); - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE); - assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(authentication.getAuthorizationUri()); - assertThat(authorizationRequest.getClientId()).isEqualTo(registeredClient.getClientId()); - - String requestUri = (String) authentication.getAdditionalParameters().get(OAuth2ParameterNames.REQUEST_URI); - if (!StringUtils.hasText(requestUri)) { - assertThat(authorizationRequest.getRedirectUri()).isEqualTo(authentication.getRedirectUri()); - assertThat(authorizationRequest.getScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorizationRequest.getState()).isEqualTo(authentication.getState()); - } - - assertThat(authorizationRequest.getAdditionalParameters()).isEqualTo(authentication.getAdditionalParameters()); - assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(this.principal.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorization.getAttribute(Principal.class.getName())).isEqualTo(this.principal); - - OAuth2Authorization.Token authorizationCode = authorization - .getToken(OAuth2AuthorizationCode.class); - Set authorizedScopes = authorization.getAuthorizedScopes(); - - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri()); - assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri()); - assertThat(authenticationResult.getScopes()).isEqualTo(authorizedScopes); - assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState()); - assertThat(authenticationResult.getAuthorizationCode()).isEqualTo(authorizationCode.getToken()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - } - - private static void assertAuthenticationException( - OAuth2AuthorizationCodeRequestAuthenticationException authenticationException, String errorCode, - String parameterName, String redirectUri) { - - OAuth2Error error = authenticationException.getError(); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).contains(parameterName); - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationException - .getAuthorizationCodeRequestAuthentication(); - assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationTokenTests.java deleted file mode 100644 index da01589aa..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationTokenTests.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. - * - * @author Joe Grandja - */ -public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests { - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize"; - - private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build(); - - private static final TestingAuthenticationToken PRINCIPAL = new TestingAuthenticationToken("principalName", - "password"); - - private static final OAuth2AuthorizationCode AUTHORIZATION_CODE = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plus(5, ChronoUnit.MINUTES)); - - @Test - public void constructorWhenAuthorizationUriNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeRequestAuthenticationToken(null, - REGISTERED_CLIENT.getClientId(), PRINCIPAL, null, null, (Set) null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationUri cannot be empty"); - } - - @Test - public void constructorWhenClientIdNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, null, - PRINCIPAL, null, null, (Set) null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientId cannot be empty"); - } - - @Test - public void constructorWhenPrincipalNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, - REGISTERED_CLIENT.getClientId(), null, null, null, (Set) null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("principal cannot be null"); - } - - @Test - public void constructorWhenAuthorizationCodeNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, - REGISTERED_CLIENT.getClientId(), PRINCIPAL, null, null, null, (Set) null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationCode cannot be null"); - } - - @Test - public void constructorWhenAuthorizationRequestThenValuesAreSet() { - String clientId = REGISTERED_CLIENT.getClientId(); - String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next(); - String state = "state"; - Set requestedScopes = REGISTERED_CLIENT.getScopes(); - Map additionalParameters = Collections.singletonMap("param1", "value1"); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, clientId, PRINCIPAL, redirectUri, state, requestedScopes, additionalParameters); - - assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL); - assertThat(authentication.getCredentials()).isEqualTo(""); - assertThat(authentication.getAuthorities()).isEmpty(); - assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI); - assertThat(authentication.getClientId()).isEqualTo(clientId); - assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri); - assertThat(authentication.getState()).isEqualTo(state); - assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(requestedScopes); - assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters); - assertThat(authentication.getAuthorizationCode()).isNull(); - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - public void constructorWhenAuthorizationResponseThenValuesAreSet() { - String clientId = REGISTERED_CLIENT.getClientId(); - String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next(); - String state = "state"; - Set authorizedScopes = REGISTERED_CLIENT.getScopes(); - - OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, clientId, PRINCIPAL, AUTHORIZATION_CODE, redirectUri, state, authorizedScopes); - - assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL); - assertThat(authentication.getCredentials()).isEqualTo(""); - assertThat(authentication.getAuthorities()).isEmpty(); - assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI); - assertThat(authentication.getClientId()).isEqualTo(clientId); - assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri); - assertThat(authentication.getState()).isEqualTo(state); - assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - assertThat(authentication.getAuthorizationCode()).isEqualTo(AUTHORIZATION_CODE); - assertThat(authentication.isAuthenticated()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContextTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContextTests.java deleted file mode 100644 index af7459911..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContextTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.security.Principal; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AuthorizationConsentAuthenticationContext}. - * - * @author Steve Riesenberg - * @author Joe Grandja - */ -public class OAuth2AuthorizationConsentAuthenticationContextTests { - - private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient) - .build(); - - private final Authentication principal = this.authorization.getAttribute(Principal.class.getName()); - - private final OAuth2AuthorizationRequest authorizationRequest = this.authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - - private final OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication = new OAuth2AuthorizationConsentAuthenticationToken( - this.authorizationRequest.getAuthorizationUri(), this.registeredClient.getClientId(), this.principal, - "state", null, null); - - private final OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = OAuth2AuthorizationConsent - .withId(this.authorization.getRegisteredClientId(), this.authorization.getPrincipalName()); - - @Test - public void withWhenAuthenticationNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2AuthorizationConsentAuthenticationContext.with(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authentication cannot be null"); - } - - @Test - public void setWhenValueNullThenThrowIllegalArgumentException() { - OAuth2AuthorizationConsentAuthenticationContext.Builder builder = OAuth2AuthorizationConsentAuthenticationContext - .with(this.authorizationConsentAuthentication); - - assertThatThrownBy(() -> builder.authorizationConsent(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.registeredClient(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.authorization(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.authorizationRequest(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.put(null, "")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenRequiredValueNullThenThrowIllegalArgumentException() { - OAuth2AuthorizationConsentAuthenticationContext.Builder builder = OAuth2AuthorizationConsentAuthenticationContext - .with(this.authorizationConsentAuthentication); - - assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentBuilder cannot be null"); - builder.authorizationConsent(this.authorizationConsentBuilder); - - assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClient cannot be null"); - builder.registeredClient(this.registeredClient); - - assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorization cannot be null"); - builder.authorization(this.authorization); - - assertThatThrownBy(builder::build).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationRequest cannot be null"); - builder.authorizationRequest(this.authorizationRequest); - - builder.build(); - } - - @Test - public void buildWhenAllValuesProvidedThenAllValuesAreSet() { - OAuth2AuthorizationConsentAuthenticationContext context = OAuth2AuthorizationConsentAuthenticationContext - .with(this.authorizationConsentAuthentication) - .authorizationConsent(this.authorizationConsentBuilder) - .registeredClient(this.registeredClient) - .authorization(this.authorization) - .authorizationRequest(this.authorizationRequest) - .put("custom-key-1", "custom-value-1") - .context((ctx) -> ctx.put("custom-key-2", "custom-value-2")) - .build(); - - assertThat(context.getAuthentication()).isEqualTo(this.authorizationConsentAuthentication); - assertThat(context.getAuthorizationConsent()).isEqualTo(this.authorizationConsentBuilder); - assertThat(context.getRegisteredClient()).isEqualTo(this.registeredClient); - assertThat(context.getAuthorization()).isEqualTo(this.authorization); - assertThat(context.getAuthorizationRequest()).isEqualTo(this.authorizationRequest); - assertThat(context.get("custom-key-1")).isEqualTo("custom-value-1"); - assertThat(context.get("custom-key-2")).isEqualTo("custom-value-2"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProviderTests.java deleted file mode 100644 index cb51847ed..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProviderTests.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.security.Principal; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2AuthorizationConsentAuthenticationProvider}. - * - * @author Joe Grandja - * @author Steve Riesenberg - */ -public class OAuth2AuthorizationConsentAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize"; - - private static final String STATE = "state"; - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2AuthorizationConsentAuthenticationProvider authenticationProvider; - - private TestingAuthenticationToken principal; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class); - this.authenticationProvider = new OAuth2AuthorizationConsentAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, this.authorizationConsentService); - this.principal = new TestingAuthenticationToken("principalName", "password"); - this.principal.setAuthenticated(true); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(null, this.authorizationService, - this.authorizationConsentService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(this.registeredClientRepository, - null, this.authorizationConsentService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenAuthorizationConsentServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2AuthorizationConsentAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2AuthorizationConsentAuthenticationToken.class)).isTrue(); - } - - @Test - public void setAuthorizationCodeGeneratorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationCodeGenerator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationCodeGenerator cannot be null"); - } - - @Test - public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationConsentCustomizer cannot be null"); - } - - @Test - public void authenticateWhenInvalidStateThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, registeredClient.getScopes(), - null); - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, registeredClient.getScopes(), - null); - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - this.principal.setAuthenticated(false); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)); - } - - @Test - public void authenticateWhenInvalidPrincipalThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName().concat("-other")) - .build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, registeredClient.getScopes(), - null); - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - given(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE))).willReturn(authorization); - RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, otherRegisteredClient.getClientId(), this.principal, STATE, - registeredClient.getScopes(), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)); - } - - @Test - public void authenticateWhenDoesNotMatchClientThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(otherRegisteredClient) - .principalName(this.principal.getName()) - .build(); - given(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE))).willReturn(authorization); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, registeredClient.getScopes(), - null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)); - } - - @Test - public void authenticateWhenScopeNotRequestedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set authorizedScopes = new HashSet<>(authorizationRequest.getScopes()); - authorizedScopes.add("scope-not-requested"); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, authorizedScopes, null); - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationRequest.getRedirectUri())); - } - - @Test - public void authenticateWhenNotApprovedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, new HashSet<>(), null); // No - // scopes - // approved - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, - authorizationRequest.getRedirectUri())); - - verify(this.authorizationService).remove(eq(authorization)); - } - - @Test - public void authenticateWhenApproveAllThenReturnAuthorizationCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set authorizedScopes = authorizationRequest.getScopes(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, authorizedScopes, null); // Approve - // all - // scopes - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, - authenticationResult); - } - - @Test - public void authenticateWhenCustomAuthorizationConsentCustomizerThenUsed() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set authorizedScopes = authorizationRequest.getScopes(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, authorizedScopes, null); // Approve - // all - // scopes - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - - @SuppressWarnings("unchecked") - Consumer authorizationConsentCustomizer = mock(Consumer.class); - this.authenticationProvider.setAuthorizationConsentCustomizer(authorizationConsentCustomizer); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, - authenticationResult); - - ArgumentCaptor authenticationContextCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationConsentAuthenticationContext.class); - verify(authorizationConsentCustomizer).accept(authenticationContextCaptor.capture()); - - OAuth2AuthorizationConsentAuthenticationContext authenticationContext = authenticationContextCaptor.getValue(); - assertThat(authenticationContext.getAuthentication()).isEqualTo(authentication); - assertThat(authenticationContext.getAuthorizationConsent()).isNotNull(); - assertThat(authenticationContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationContext.getAuthorization()).isEqualTo(authorization); - assertThat(authenticationContext.getAuthorizationRequest()).isEqualTo(authorizationRequest); - } - - private void assertAuthorizationConsentRequestWithAuthorizationCodeResult(RegisteredClient registeredClient, - OAuth2Authorization authorization, OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) { - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set authorizedScopes = authorizationRequest.getScopes(); - - ArgumentCaptor authorizationConsentCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationConsent.class); - verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture()); - OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue(); - - assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId()); - assertThat(authorizationConsent.getPrincipalName()).isEqualTo(authorization.getPrincipalName()); - assertThat(authorizationConsent.getAuthorities()).hasSize(authorizedScopes.size()); - assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(updatedAuthorization.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId()); - assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authorization.getPrincipalName()); - assertThat(updatedAuthorization.getAuthorizationGrantType()) - .isEqualTo(authorization.getAuthorizationGrantType()); - assertThat(updatedAuthorization.getAttribute(Principal.class.getName())) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(updatedAuthorization - .getAttribute(OAuth2AuthorizationRequest.class.getName())) - .isEqualTo(authorizationRequest); - OAuth2Authorization.Token authorizationCode = updatedAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCode).isNotNull(); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)).isNull(); - assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(authorizedScopes); - - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri()); - assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri()); - assertThat(authenticationResult.getScopes()).isEqualTo(authorizedScopes); - assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState()); - assertThat(authenticationResult.getAuthorizationCode()).isEqualTo(authorizationCode.getToken()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - } - - @Test - public void authenticateWhenApproveNoneAndRevokePreviouslyApprovedThenAuthorizationConsentRemoved() { - String previouslyApprovedScope = "message.read"; - String requestedScope = "message.write"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(previouslyApprovedScope); - scopes.add(requestedScope); - }).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, new HashSet<>(), null); // No - // scopes - // approved - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - OAuth2AuthorizationConsent previousAuthorizationConsent = OAuth2AuthorizationConsent - .withId(authorization.getRegisteredClientId(), authorization.getPrincipalName()) - .scope(previouslyApprovedScope) - .build(); - given(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), - eq(authorization.getPrincipalName()))) - .willReturn(previousAuthorizationConsent); - - // Revoke all (including previously approved) - this.authenticationProvider.setAuthorizationConsentCustomizer( - (authorizationConsentContext) -> authorizationConsentContext.getAuthorizationConsent() - .authorities(Set::clear)); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, - authorizationRequest.getRedirectUri())); - - verify(this.authorizationConsentService).remove(eq(previousAuthorizationConsent)); - verify(this.authorizationService).remove(eq(authorization)); - } - - @Test - public void authenticateWhenApproveSomeAndPreviouslyApprovedThenAuthorizationConsentUpdated() { - String previouslyApprovedScope = "message.read"; - String requestedScope = "message.write"; - String otherPreviouslyApprovedScope = "other.scope"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(previouslyApprovedScope); - scopes.add(requestedScope); - }).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - Set requestedScopes = authorizationRequest.getScopes(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, requestedScopes, null); - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - OAuth2AuthorizationConsent previousAuthorizationConsent = OAuth2AuthorizationConsent - .withId(authorization.getRegisteredClientId(), authorization.getPrincipalName()) - .scope(previouslyApprovedScope) - .scope(otherPreviouslyApprovedScope) - .build(); - given(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), - eq(authorization.getPrincipalName()))) - .willReturn(previousAuthorizationConsent); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor authorizationConsentCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationConsent.class); - verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture()); - OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue(); - - assertThat(updatedAuthorizationConsent.getRegisteredClientId()) - .isEqualTo(previousAuthorizationConsent.getRegisteredClientId()); - assertThat(updatedAuthorizationConsent.getPrincipalName()) - .isEqualTo(previousAuthorizationConsent.getPrincipalName()); - assertThat(updatedAuthorizationConsent.getScopes()).containsExactlyInAnyOrder(previouslyApprovedScope, - otherPreviouslyApprovedScope, requestedScope); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(requestedScopes); - assertThat(authenticationResult.getScopes()).isEqualTo(requestedScopes); - } - - @Test - public void authenticateWhenApproveNoneAndPreviouslyApprovedThenAuthorizationConsentNotUpdated() { - String previouslyApprovedScope = "message.read"; - String requestedScope = "message.write"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(previouslyApprovedScope); - scopes.add(requestedScope); - }).build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(this.principal.getName()) - .build(); - OAuth2AuthorizationConsentAuthenticationToken authentication = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, new HashSet<>(), null); // No - // scopes - // approved - given(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE))) - .willReturn(authorization); - OAuth2AuthorizationConsent previousAuthorizationConsent = OAuth2AuthorizationConsent - .withId(authorization.getRegisteredClientId(), authorization.getPrincipalName()) - .scope(previouslyApprovedScope) - .build(); - given(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), - eq(authorization.getPrincipalName()))) - .willReturn(previousAuthorizationConsent); - - OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationConsentService, never()).save(any()); - assertThat(authenticationResult.getScopes()).isEqualTo(Collections.singleton(previouslyApprovedScope)); - } - - private static void assertAuthenticationException( - OAuth2AuthorizationCodeRequestAuthenticationException authenticationException, String errorCode, - String parameterName, String redirectUri) { - - OAuth2Error error = authenticationException.getError(); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).contains(parameterName); - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationException - .getAuthorizationCodeRequestAuthentication(); - assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java deleted file mode 100644 index d29495821..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2ClientAuthenticationToken}. - * - * @author Joe Grandja - * @author Anoop Garlapati - */ -public class OAuth2ClientAuthenticationTokenTests { - - @Test - public void constructorWhenClientIdNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, "secret", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientId cannot be empty"); - } - - @Test - public void constructorWhenClientAuthenticationMethodNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null, "clientSecret", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientAuthenticationMethod cannot be null"); - } - - @Test - public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, "clientSecret")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClient cannot be null"); - } - - @Test - public void constructorWhenClientCredentialsProvidedThenCreated() { - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, "secret", null); - assertThat(authentication.isAuthenticated()).isFalse(); - assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId"); - assertThat(authentication.getCredentials()).isEqualTo("secret"); - assertThat(authentication.getRegisteredClient()).isNull(); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - } - - @Test - public void constructorWhenRegisteredClientProvidedThenCreated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - assertThat(authentication.isAuthenticated()).isTrue(); - assertThat(authentication.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authentication.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret()); - assertThat(authentication.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java deleted file mode 100644 index 6ad95f50a..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2ClientCredentialsAuthenticationProvider}. - * - * @author Alexey Nesterov - * @author Joe Grandja - */ -public class OAuth2ClientCredentialsAuthenticationProviderTests { - - private OAuth2AuthorizationService authorizationService; - - private JwtEncoder jwtEncoder; - - private OAuth2TokenCustomizer jwtCustomizer; - - private OAuth2TokenCustomizer accessTokenCustomizer; - - private OAuth2TokenGenerator tokenGenerator; - - private JwtEncoder dPoPProofJwtEncoder; - - private OAuth2ClientCredentialsAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.jwtEncoder = mock(JwtEncoder.class); - this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); - JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder); - jwtGenerator.setJwtCustomizer(this.jwtCustomizer); - this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); - accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, - accessTokenGenerator); - this.tokenGenerator = spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - this.dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - this.authenticationProvider = new OAuth2ClientCredentialsAuthenticationProvider(this.authorizationService, - this.tokenGenerator); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientCredentialsAuthenticationProvider(null, this.tokenGenerator)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientCredentialsAuthenticationProvider(this.authorizationService, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenGenerator cannot be null"); - } - - @Test - public void supportsWhenSupportedAuthenticationThenTrue() { - assertThat(this.authenticationProvider.supports(OAuth2ClientCredentialsAuthenticationToken.class)).isTrue(); - } - - @Test - public void supportsWhenUnsupportedAuthenticationThenFalse() { - assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isFalse(); - } - - @Test - public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationValidator cannot be null"); - } - - @Test - public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(registeredClient.getClientId(), - registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientNotAuthorizedToRequestTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .authorizationGrantTypes((grantTypes) -> grantTypes.remove(AuthorizationGrantType.CLIENT_CREDENTIALS)) - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - } - - @Test - public void authenticateWhenInvalidScopeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, Collections.singleton("invalid-scope"), null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - } - - @Test - public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - Set requestedScope = Collections.singleton("scope1"); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, requestedScope, null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt(Collections.singleton("mapped-scoped"))); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(requestedScope); - } - - @Test - public void authenticateWhenNoScopeRequestedThenAccessTokenDoesNotContainScope() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt(Collections.singleton("mapped-scoped"))); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEmpty(); - } - - @Test - public void authenticateWhenAccessTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - doReturn(null).when(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()).contains("The token generator failed to generate the access token."); - }); - } - - @Test - public void authenticateWhenValidAuthenticationThenReturnAccessToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - Map additionalParameters = new HashMap<>(); - additionalParameters.put("dpop_proof", generateDPoPProof("http://localhost/oauth2/token")); - additionalParameters.put("dpop_method", "POST"); - additionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, additionalParameters); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt(registeredClient.getScopes())); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(jwtEncodingContext.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(jwtEncodingContext.getAuthorization()).isNull(); - assertThat(jwtEncodingContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - assertThat(jwtEncodingContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(jwtEncodingContext.getJwsHeader()).isNotNull(); - assertThat(jwtEncodingContext.getClaims()).isNotNull(); - assertThat(jwtEncodingContext.get(OAuth2TokenContext.DPOP_PROOF_KEY)).isNotNull(); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(jwtEncodingContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(authorization.getRegisteredClientId()).isEqualTo(clientPrincipal.getRegisteredClient().getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(clientPrincipal.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getAuthorizedScopes()).isNotNull(); - assertThat(authorization.getAccessToken().getToken().getScopes()) - .isEqualTo(authorization.getAuthorizedScopes()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(authorization.getAccessToken().getToken()); - } - - @Test - public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .tokenSettings(TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build()) - .build(); - // @formatter:on - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - - this.authenticationProvider.authenticate(authentication); - - verify(this.accessTokenCustomizer).customize(any()); - } - - @Test - public void authenticateWhenCustomAuthenticationValidatorThenUsed() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, registeredClient.getScopes(), null); - - @SuppressWarnings("unchecked") - Consumer authenticationValidator = mock(Consumer.class); - this.authenticationProvider.setAuthenticationValidator(authenticationValidator); - - given(this.jwtEncoder.encode(any())).willReturn(createJwt(registeredClient.getScopes())); - - this.authenticationProvider.authenticate(authentication); - - verify(authenticationValidator).accept(any(OAuth2ClientCredentialsAuthenticationContext.class)); - } - - private static Jwt createJwt(Set scope) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return Jwt.withTokenValue("token") - .header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName()) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .claim(OAuth2ParameterNames.SCOPE, scope) - .build(); - } - - private String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = this.dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java deleted file mode 100644 index 4a12286ee..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2ClientCredentialsAuthenticationToken}. - * - * @author Alexey Nesterov - */ -public class OAuth2ClientCredentialsAuthenticationTokenTests { - - private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private final OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - this.registeredClient.getClientSecret()); - - private Set scopes = Collections.singleton("scope1"); - - private Map additionalParameters = Collections.singletonMap("param1", "value1"); - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2ClientCredentialsAuthenticationToken(null, this.scopes, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenClientPrincipalProvidedThenCreated() { - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - this.clientPrincipal, this.scopes, this.additionalParameters); - - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getScopes()).isEqualTo(this.scopes); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - - @Test - public void constructorWhenScopesProvidedThenCreated() { - Set expectedScopes = Collections.singleton("test-scope"); - - OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken( - this.clientPrincipal, expectedScopes, this.additionalParameters); - - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getScopes()).isEqualTo(expectedScopes); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProviderTests.java deleted file mode 100644 index 86e900af1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProviderTests.java +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -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.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests for {@link OAuth2DeviceAuthorizationConsentAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceAuthorizationConsentAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "/oauth2/device_authorization"; - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private static final String STATE = "abc123"; - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2DeviceAuthorizationConsentAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class); - this.authenticationProvider = new OAuth2DeviceAuthorizationConsentAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, this.authorizationConsentService); - } - - @Test - public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationConsentAuthenticationProvider( - null, this.authorizationService, this.authorizationConsentService)) - .withMessage("registeredClientRepository cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenAuthorizationServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationConsentAuthenticationProvider( - this.registeredClientRepository, null, this.authorizationConsentService)) - .withMessage("authorizationService cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenAuthorizationConsentServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationConsentAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, null)) - .withMessage("authorizationConsentService cannot be null"); - // @formatter:on - } - - @Test - public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null)) - .withMessageContaining("authorizationConsentCustomizer cannot be null"); - // @formatter:on - } - - @Test - public void supportsWhenTypeOAuth2DeviceAuthorizationConsentAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2DeviceAuthorizationConsentAuthenticationToken.class)) - .isTrue(); - } - - @Test - public void authenticateWhenAuthorizationNotFoundThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.STATE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenPrincipalIsNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - TestingAuthenticationToken principal = new TestingAuthenticationToken(authorization.getPrincipalName(), null); - Authentication authentication = new OAuth2DeviceAuthorizationConsentAuthenticationToken(AUTHORIZATION_URI, - registeredClient.getClientId(), principal, USER_CODE, STATE, null, Collections.emptyMap()); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.STATE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenPrincipalNameDoesNotMatchThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - TestingAuthenticationToken principal = new TestingAuthenticationToken("invalid", null, Collections.emptyList()); - Authentication authentication = new OAuth2DeviceAuthorizationConsentAuthenticationToken(AUTHORIZATION_URI, - registeredClient.getClientId(), principal, USER_CODE, STATE, null, Collections.emptyMap()); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.STATE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService); - verifyNoInteractions(this.authorizationConsentService); - } - - @Test - public void authenticateWhenRegisteredClientDoesNotMatchAuthorizationThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().build(); - OAuth2Authorization authorization = createAuthorization(registeredClient2); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService); - verifyNoInteractions(this.authorizationConsentService); - } - - @Test - public void authenticateWhenRequestedScopesNotAuthorizedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient() - .scopes(Set::clear) - .scope("invalid") - .build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - Authentication authentication = createAuthentication(registeredClient2); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.SCOPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - // @formatter:on - - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService); - verifyNoInteractions(this.authorizationConsentService); - } - - @Test - public void authenticateWhenAuthoritiesIsEmptyThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient().scopes(Set::clear).build(); - OAuth2Authorization authorization = createAuthorization(registeredClient2); - Authentication authentication = createAuthentication(registeredClient2); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.ACCESS_DENIED); - // @formatter:on - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)).isNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void authenticateWhenAuthoritiesIsNotEmptyThenAuthorizationConsentSaved() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - - Authentication authentication = createAuthentication(registeredClient); - OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isSameAs(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verify(this.authorizationConsentService).save(any(OAuth2AuthorizationConsent.class)); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(updatedAuthorization.getAuthorizedScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)).isNull(); - assertThat(updatedAuthorization.>getAttribute(OAuth2ParameterNames.SCOPE)).isNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(false); - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void authenticateWhenExistingAuthorizationConsentThenUpdated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope("additional").build(); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient() - .scopes(Set::clear) - .scope("additional") - .build(); - OAuth2Authorization authorization = createAuthorization(registeredClient2); - Authentication authentication = createAuthentication(registeredClient2); - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getId(), authentication.getName()) - .scope("scope1").build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - given(this.authorizationConsentService.findById(anyString(), anyString())).willReturn(authorizationConsent); - - OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isSameAs(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - - ArgumentCaptor authorizationConsentCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationConsent.class); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture()); - verify(this.authorizationService).save(any(OAuth2Authorization.class)); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue(); - assertThat(updatedAuthorizationConsent.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(updatedAuthorizationConsent.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(updatedAuthorizationConsent.getScopes()).hasSameElementsAs(registeredClient.getScopes()); - } - - @Test - public void authenticateWhenAuthorizationConsentCustomizerSetThenUsed() { - SimpleGrantedAuthority customAuthority = new SimpleGrantedAuthority("test"); - this.authenticationProvider.setAuthorizationConsentCustomizer( - (context) -> context.getAuthorizationConsent().authority(customAuthority)); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes(Set::clear).build(); - OAuth2Authorization authorization = createAuthorization(registeredClient); - Authentication authentication = createAuthentication(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.registeredClientRepository.findByClientId(anyString())).willReturn(registeredClient); - given(this.authorizationConsentService.findById(anyString(), anyString())).willReturn(null); - - OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isSameAs(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - - ArgumentCaptor authorizationConsentCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationConsent.class); - verify(this.authorizationService).findByToken(STATE, - OAuth2DeviceAuthorizationConsentAuthenticationProvider.STATE_TOKEN_TYPE); - verify(this.registeredClientRepository).findByClientId(registeredClient.getClientId()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture()); - verify(this.authorizationService).save(any(OAuth2Authorization.class)); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue(); - assertThat(updatedAuthorizationConsent.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(updatedAuthorizationConsent.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(updatedAuthorizationConsent.getAuthorities()).containsExactly(customAuthority); - } - - private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { - // @formatter:off - return TestOAuth2Authorizations.authorization(registeredClient) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(createDeviceCode()) - .token(createUserCode()) - .attributes(Map::clear) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - } - - private static OAuth2DeviceAuthorizationConsentAuthenticationToken createAuthentication( - RegisteredClient registeredClient) { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", null, - Collections.emptyList()); - Set authorizedScopes = registeredClient.getScopes(); - if (authorizedScopes.isEmpty()) { - authorizedScopes = null; - } - Map additionalParameters = null; - return new OAuth2DeviceAuthorizationConsentAuthenticationToken(AUTHORIZATION_URI, - registeredClient.getClientId(), principal, USER_CODE, STATE, authorizedScopes, additionalParameters); - } - - private static OAuth2DeviceCode createDeviceCode() { - Instant issuedAt = Instant.now(); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createUserCode() { - Instant issuedAt = Instant.now(); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java deleted file mode 100644 index 81b038f6d..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.authentication; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Set; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -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; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests for {@link OAuth2DeviceAuthorizationRequestAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceAuthorizationRequestAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "/oauth2/device_authorization"; - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2DeviceAuthorizationRequestAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new OAuth2DeviceAuthorizationRequestAuthenticationProvider( - this.authorizationService); - mockAuthorizationServerContext(); - } - - @AfterEach - public void tearDown() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationRequestAuthenticationProvider(null)) - .withMessage("authorizationService cannot be null"); - // @formatter:on - } - - @Test - public void setDeviceCodeGeneratorWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setDeviceCodeGenerator(null)) - .withMessage("deviceCodeGenerator cannot be null"); - // @formatter:on - } - - @Test - public void setUserCodeGeneratorWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setUserCodeGenerator(null)) - .withMessage("userCodeGenerator cannot be null"); - // @formatter:on - } - - @Test - public void supportsWhenTypeOAuth2DeviceAuthorizationRequestAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2DeviceAuthorizationRequestAuthenticationToken.class)) - .isTrue(); - } - - @Test - public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken("client-1", - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); - OAuth2DeviceAuthorizationRequestAuthenticationToken authentication = new OAuth2DeviceAuthorizationRequestAuthenticationToken( - clientPrincipal, AUTHORIZATION_URI, null, null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenInvalidGrantTypeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenInvalidScopesThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - Authentication authentication = new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, - AUTHORIZATION_URI, Collections.singleton("invalid"), null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining(OAuth2ParameterNames.SCOPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - // @formatter:on - } - - @Test - public void authenticateWhenDeviceCodeIsNullThenThrowOAuth2AuthenticationException() { - @SuppressWarnings("unchecked") - OAuth2TokenGenerator deviceCodeGenerator = mock(OAuth2TokenGenerator.class); - given(deviceCodeGenerator.generate(any(OAuth2TokenContext.class))).willReturn(null); - this.authenticationProvider.setDeviceCodeGenerator(deviceCodeGenerator); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining("The token generator failed to generate the device code.") - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - // @formatter:on - - verify(deviceCodeGenerator).generate(any(OAuth2TokenContext.class)); - verifyNoMoreInteractions(deviceCodeGenerator); - verifyNoInteractions(this.authorizationService); - } - - @Test - public void authenticateWhenUserCodeIsNullThenThrowOAuth2AuthenticationException() { - @SuppressWarnings("unchecked") - OAuth2TokenGenerator userCodeGenerator = mock(OAuth2TokenGenerator.class); - given(userCodeGenerator.generate(any(OAuth2TokenContext.class))).willReturn(null); - this.authenticationProvider.setUserCodeGenerator(userCodeGenerator); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessageContaining("The token generator failed to generate the user code.") - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - // @formatter:on - - verify(userCodeGenerator).generate(any(OAuth2TokenContext.class)); - verifyNoMoreInteractions(userCodeGenerator); - verifyNoInteractions(this.authorizationService); - } - - @Test - public void authenticateWhenScopesRequestedThenReturnDeviceCodeAndUserCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2DeviceAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getDeviceCode().getTokenValue()).hasSize(128); - // 8 chars + 1 dash - assertThat(authenticationResult.getUserCode().getTokenValue()).hasSize(9); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService); - - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.DEVICE_CODE); - assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull(); - assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull(); - assertThat(authorization.>getAttribute(OAuth2ParameterNames.SCOPE)) - .hasSameElementsAs(registeredClient.getScopes()); - } - - @Test - public void authenticateWhenNoScopesRequestedThenReturnDeviceCodeAndUserCode() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scopes(Set::clear) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2DeviceAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getDeviceCode().getTokenValue()).hasSize(128); - // 8 chars + 1 dash - assertThat(authenticationResult.getUserCode().getTokenValue()).hasSize(9); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService); - - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.DEVICE_CODE); - assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull(); - assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull(); - assertThat(authorization.>getAttribute(OAuth2ParameterNames.SCOPE)) - .hasSameElementsAs(registeredClient.getScopes()); - } - - @Test - public void authenticateWhenDeviceCodeGeneratorSetThenUsed() { - @SuppressWarnings("unchecked") - OAuth2TokenGenerator deviceCodeGenerator = mock(OAuth2TokenGenerator.class); - given(deviceCodeGenerator.generate(any(OAuth2TokenContext.class))).willReturn(createDeviceCode()); - this.authenticationProvider.setDeviceCodeGenerator(deviceCodeGenerator); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2DeviceAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getDeviceCode().getTokenValue()).isEqualTo(DEVICE_CODE); - // 8 chars + 1 dash - assertThat(authenticationResult.getUserCode().getTokenValue()).hasSize(9); - - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(deviceCodeGenerator).generate(tokenContextCaptor.capture()); - verify(this.authorizationService).save(any(OAuth2Authorization.class)); - verifyNoMoreInteractions(this.authorizationService, deviceCodeGenerator); - - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.DEVICE_CODE); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.getTokenType()) - .isEqualTo(OAuth2DeviceAuthorizationRequestAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - } - - @Test - public void authenticateWhenUserCodeGeneratorSetThenUsed() { - @SuppressWarnings("unchecked") - OAuth2TokenGenerator userCodeGenerator = mock(OAuth2TokenGenerator.class); - given(userCodeGenerator.generate(any(OAuth2TokenContext.class))).willReturn(createUserCode()); - this.authenticationProvider.setUserCodeGenerator(userCodeGenerator); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2DeviceAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getDeviceCode().getTokenValue()).hasSize(128); - assertThat(authenticationResult.getUserCode().getTokenValue()).isEqualTo(USER_CODE); - - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(userCodeGenerator).generate(tokenContextCaptor.capture()); - verify(this.authorizationService).save(any(OAuth2Authorization.class)); - verifyNoMoreInteractions(this.authorizationService, userCodeGenerator); - - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.DEVICE_CODE); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.getTokenType()) - .isEqualTo(OAuth2DeviceAuthorizationRequestAuthenticationProvider.USER_CODE_TOKEN_TYPE); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> "https://provider.com"); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static OAuth2DeviceAuthorizationRequestAuthenticationToken createAuthentication( - RegisteredClient registeredClient) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - Set requestedScopes = registeredClient.getScopes(); - if (requestedScopes.isEmpty()) { - requestedScopes = null; - } - return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, AUTHORIZATION_URI, - requestedScopes, null); - } - - private static OAuth2DeviceCode createDeviceCode() { - Instant issuedAt = Instant.now(); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createUserCode() { - Instant issuedAt = Instant.now(); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java deleted file mode 100644 index 4a124b0e2..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProviderTests.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.core.Authentication; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -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.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -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.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests for {@link OAuth2DeviceCodeAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceCodeAuthenticationProviderTests { - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private static final String ACCESS_TOKEN = "abc123"; - - private static final String REFRESH_TOKEN = "xyz456"; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2TokenGenerator tokenGenerator; - - private JwtEncoder dPoPProofJwtEncoder; - - private OAuth2DeviceCodeAuthenticationProvider authenticationProvider; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.tokenGenerator = mock(OAuth2TokenGenerator.class); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - this.dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - this.authenticationProvider = new OAuth2DeviceCodeAuthenticationProvider(this.authorizationService, - this.tokenGenerator); - mockAuthorizationServerContext(); - } - - @AfterEach - public void tearDown() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceCodeAuthenticationProvider(null, this.tokenGenerator)) - .withMessage("authorizationService cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenTokenGeneratorIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceCodeAuthenticationProvider(this.authorizationService, null)) - .withMessage("tokenGenerator cannot be null"); - // @formatter:on - } - - @Test - public void supportsWhenTypeOAuth2DeviceCodeAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2DeviceCodeAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken("client-1", - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); - Authentication authentication = new OAuth2DeviceCodeAuthenticationToken(DEVICE_CODE, clientPrincipal, null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenAuthorizationNotFoundThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenRegisteredClientDoesNotMatchClientIdThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient2) - .token(createDeviceCode()) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void authenticateWhenUserCodeIsNotInvalidatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode()) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2DeviceCodeAuthenticationProvider.AUTHORIZATION_PENDING); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenDeviceCodeAndUserCodeAreInvalidatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode(), withInvalidated()) - .token(createUserCode(), withInvalidated()) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.ACCESS_DENIED); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenDeviceCodeIsExpiredThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createExpiredDeviceCode()) - .token(createUserCode()) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2DeviceCodeAuthenticationProvider.EXPIRED_TOKEN); - // @formatter:on - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void authenticateWhenAccessTokenIsNullThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode(), withInvalidated()) - .attribute(Principal.class.getName(), authentication.getPrincipal()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessage("The token generator failed to generate the access token.") - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.tokenGenerator).generate(any(OAuth2TokenContext.class)); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - } - - @Test - public void authenticateWhenRefreshTokenIsNullThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode(), withInvalidated()) - .attribute(Principal.class.getName(), authentication.getPrincipal()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(createAccessToken(), - (OAuth2RefreshToken) null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessage("The token generator failed to generate the refresh token.") - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.tokenGenerator, times(2)).generate(any(OAuth2TokenContext.class)); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - } - - @Test - public void authenticateWhenTokenGeneratorReturnsWrongTypeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication authentication = createAuthentication(registeredClient); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode(), withInvalidated()) - .attribute(Principal.class.getName(), authentication.getPrincipal()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - OAuth2AccessToken accessToken = createAccessToken(); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(accessToken, accessToken); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .withMessage("The token generator failed to generate the refresh token.") - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - // @formatter:on - - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.tokenGenerator, times(2)).generate(any(OAuth2TokenContext.class)); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - } - - @Test - public void authenticateWhenValidDeviceCodeThenReturnAccessTokenAndRefreshToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - Map additionalParameters = new HashMap<>(); - additionalParameters.put("dpop_proof", generateDPoPProof("http://localhost/oauth2/token")); - additionalParameters.put("dpop_method", "POST"); - additionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - OAuth2DeviceCodeAuthenticationToken authentication = new OAuth2DeviceCodeAuthenticationToken(DEVICE_CODE, - clientPrincipal, additionalParameters); - - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode(), withInvalidated()) - .attribute(Principal.class.getName(), authentication.getPrincipal()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - OAuth2AccessToken accessToken = createAccessToken(); - OAuth2RefreshToken refreshToken = createRefreshToken(); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(accessToken, refreshToken); - OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); - assertThat(authenticationResult.getRefreshToken()).isEqualTo(refreshToken); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.authorizationService).findByToken(DEVICE_CODE, - OAuth2DeviceCodeAuthenticationProvider.DEVICE_CODE_TOKEN_TYPE); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verify(this.tokenGenerator, times(2)).generate(tokenContextCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - assertThat(updatedAuthorization.getAccessToken().getToken()).isEqualTo(accessToken); - assertThat(updatedAuthorization.getRefreshToken().getToken()).isEqualTo(refreshToken); - - for (OAuth2TokenContext tokenContext : tokenContextCaptor.getAllValues()) { - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorization()).isEqualTo(authorization); - assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.DEVICE_CODE); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.get(OAuth2TokenContext.DPOP_PROOF_KEY)).isNotNull(); - } - assertThat(tokenContextCaptor.getAllValues().get(0).getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(tokenContextCaptor.getAllValues().get(1).getTokenType()).isEqualTo(OAuth2TokenType.REFRESH_TOKEN); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> "https://provider.com"); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static OAuth2DeviceCodeAuthenticationToken createAuthentication(RegisteredClient registeredClient) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - return new OAuth2DeviceCodeAuthenticationToken(DEVICE_CODE, clientPrincipal, null); - } - - private static OAuth2DeviceCode createDeviceCode() { - Instant issuedAt = Instant.now(); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2DeviceCode createExpiredDeviceCode() { - Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createUserCode() { - Instant issuedAt = Instant.now(); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2AccessToken createAccessToken() { - Instant issuedAt = Instant.now(); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, ACCESS_TOKEN, issuedAt, - issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2RefreshToken createRefreshToken() { - Instant issuedAt = Instant.now(); - return new OAuth2RefreshToken(REFRESH_TOKEN, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - public static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - - private String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = this.dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java deleted file mode 100644 index fd6a54d60..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProviderTests.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -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.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests for {@link OAuth2DeviceVerificationAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceVerificationAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "/oauth2/device_verification"; - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2AuthorizationConsentService authorizationConsentService; - - private OAuth2DeviceVerificationAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class); - this.authenticationProvider = new OAuth2DeviceVerificationAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, this.authorizationConsentService); - mockAuthorizationServerContext(); - } - - @Test - public void constructorWhenRegisteredClientRepositoryIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceVerificationAuthenticationProvider( - null, this.authorizationService, this.authorizationConsentService)) - .withMessage("registeredClientRepository cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenAuthorizationServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceVerificationAuthenticationProvider( - this.registeredClientRepository, null, this.authorizationConsentService)) - .withMessage("authorizationService cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenAuthorizationConsentServiceIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceVerificationAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, null)) - .withMessage("authorizationConsentService cannot be null"); - // @formatter:on - } - - @Test - public void supportsWhenTypeOAuth2DeviceVerificationAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2DeviceVerificationAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenAuthorizationNotFoundThenThrowOAuth2AuthenticationException() { - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(null); - Authentication authentication = createAuthentication(); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenUserCodeIsInvalidatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode(), withInvalidated()) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(eq(USER_CODE), - eq(OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE))) - .willReturn(authorization); - Authentication authentication = createAuthentication(); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenUserCodeIsExpiredAndNotInvalidatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient) - // Device code would also be expired but not relevant for this test - .token(createDeviceCode()) - .token(createExpiredUserCode()) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(eq(USER_CODE), - eq(OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE))) - .willReturn(authorization); - Authentication authentication = createAuthentication(); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)).extracting(isInvalidated()).isEqualTo(true); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenReturnUnauthenticated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode()) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null); - Authentication authentication = new OAuth2DeviceVerificationAuthenticationToken(principal, USER_CODE, - Collections.emptyMap()); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - - OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult).isEqualTo(authentication); - assertThat(authenticationResult.isAuthenticated()).isFalse(); - - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.registeredClientRepository, this.authorizationConsentService); - } - - @Test - public void authenticateWhenAuthorizationConsentDoesNotExistThenReturnAuthorizationConsentWithState() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createDeviceCode()) - .token(createUserCode()) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - Authentication authentication = createAuthentication(); - given(this.registeredClientRepository.findById(anyString())).willReturn(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.authorizationConsentService.findById(anyString(), anyString())).willReturn(null); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - assertThat(authenticationResult.getState()).hasSize(44); - assertThat(authenticationResult.getRequestedScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getScopes()).isEmpty(); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verify(this.registeredClientRepository).findById(authorization.getRegisteredClientId()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)) - .isEqualTo(authenticationResult.getState()); - } - - @Test - public void authenticateWhenAuthorizationConsentExistsAndRequestedScopesMatchThenReturnDeviceVerification() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(createDeviceCode()) - .token(createUserCode()) - .attributes(Map::clear) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - Authentication authentication = createAuthentication(); - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getId(), authentication.getName()) - .scope(registeredClient.getScopes().iterator().next()) - .build(); - // @formatter:on - given(this.registeredClientRepository.findById(anyString())).willReturn(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.authorizationConsentService.findById(anyString(), anyString())).willReturn(authorizationConsent); - - OAuth2DeviceVerificationAuthenticationToken authenticationResult = (OAuth2DeviceVerificationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verify(this.registeredClientRepository).findById(authorization.getRegisteredClientId()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(updatedAuthorization.getAuthorizedScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(updatedAuthorization.getAttribute(Principal.class.getName())) - .isEqualTo(authentication.getPrincipal()); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)).isNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(false); - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void authenticateWhenAuthorizationConsentExistsAndRequestedScopesDoNotMatchThenReturnAuthorizationConsentWithState() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(createDeviceCode()) - .token(createUserCode()) - .attributes(Map::clear) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - Authentication authentication = createAuthentication(); - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getId(), authentication.getName()) - .scope("previous") - .build(); - // @formatter:on - given(this.registeredClientRepository.findById(anyString())).willReturn(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - given(this.authorizationConsentService.findById(anyString(), anyString())).willReturn(authorizationConsent); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authenticationResult = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getUserCode()).isEqualTo(USER_CODE); - assertThat(authenticationResult.getState()).hasSize(44); - assertThat(authenticationResult.getRequestedScopes()).hasSameElementsAs(registeredClient.getScopes()); - assertThat(authenticationResult.getScopes()).containsExactly("previous"); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).findByToken(USER_CODE, - OAuth2DeviceVerificationAuthenticationProvider.USER_CODE_TOKEN_TYPE); - verify(this.registeredClientRepository).findById(authorization.getRegisteredClientId()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verify(this.authorizationConsentService).findById(registeredClient.getId(), authentication.getName()); - verifyNoMoreInteractions(this.registeredClientRepository, this.authorizationService, - this.authorizationConsentService); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - assertThat(updatedAuthorization.getAttribute(OAuth2ParameterNames.STATE)) - .isEqualTo(authenticationResult.getState()); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> "https://provider.com"); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static OAuth2DeviceVerificationAuthenticationToken createAuthentication() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null, - AuthorityUtils.createAuthorityList("USER")); - return new OAuth2DeviceVerificationAuthenticationToken(principal, USER_CODE, Collections.emptyMap()); - } - - private static OAuth2DeviceCode createDeviceCode() { - Instant issuedAt = Instant.now(); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createUserCode() { - Instant issuedAt = Instant.now(); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createExpiredUserCode() { - Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java deleted file mode 100644 index df4e8d7a6..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2PushedAuthorizationRequestAuthenticationProvider}. - * - * @author Joe Grandja - */ -public class OAuth2PushedAuthorizationRequestAuthenticationProviderTests { - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/par"; - - private static final String STATE = "state"; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2PushedAuthorizationRequestAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new OAuth2PushedAuthorizationRequestAuthenticationProvider( - this.authorizationService); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2PushedAuthorizationRequestAuthenticationProvider(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2PushedAuthorizationRequestAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2PushedAuthorizationRequestAuthenticationToken.class)) - .isTrue(); - } - - @Test - public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationValidator cannot be null"); - } - - @Test - public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenClientNotAuthorizedToRequestCodeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantTypes(Set::clear) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, null, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenInvalidRedirectUriHostThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, "https:///invalid", STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenInvalidRedirectUriFragmentThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, "https://example.com#fragment", - STATE, registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenUnregisteredRedirectUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, "https://invalid-example.com", - STATE, registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenRedirectUriIPv4LoopbackAndDifferentPortThenReturnPushedAuthorizationResponse() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://127.0.0.1:8080") - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, "https://127.0.0.1:5000", STATE, - registeredClient.getScopes(), null); - OAuth2PushedAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2PushedAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertPushedAuthorizationResponse(registeredClient, authentication, authenticationResult); - } - - @Test - public void authenticateWhenRedirectUriIPv6LoopbackAndDifferentPortThenReturnPushedAuthorizationResponse() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://[::1]:8080") - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, "https://[::1]:5000", STATE, - registeredClient.getScopes(), null); - OAuth2PushedAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2PushedAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertPushedAuthorizationResponse(registeredClient, authentication, authenticationResult); - } - - @Test - public void authenticateWhenMissingRedirectUriAndMultipleRegisteredThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .redirectUri("https://example2.com") - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, null, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenAuthenticationRequestMissingRedirectUriThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - // redirect_uri is REQUIRED for OpenID Connect requests - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, null, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, null)); - } - - @Test - public void authenticateWhenInvalidScopeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - Collections.singleton("invalid-scope"), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPkceRequiredAndMissingCodeChallengeThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireProofKey(true).build()) - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPkceUnsupportedCodeChallengeMethodThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported"); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPkceMissingCodeChallengeMethodThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, - authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneLoginThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithInvalidPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none login"); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneConsentThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithInvalidPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none consent"); - } - - @Test - public void authenticateWhenAuthenticationRequestWithPromptNoneSelectAccountThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { - assertWhenAuthenticationRequestWithInvalidPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - "none select_account"); - } - - private void assertWhenAuthenticationRequestWithInvalidPromptThenThrowOAuth2AuthorizationCodeRequestAuthenticationException( - String prompt) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put("prompt", prompt); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) - .satisfies((ex) -> assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex, - OAuth2ErrorCodes.INVALID_REQUEST, "prompt", authentication.getRedirectUri())); - } - - @Test - public void authenticateWhenPushedAuthorizationRequestValidThenReturnPushedAuthorizationResponse() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), additionalParameters); - OAuth2PushedAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2PushedAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertPushedAuthorizationResponse(registeredClient, authentication, authenticationResult); - } - - @Test - public void authenticateWhenCustomAuthenticationValidatorThenUsed() { - @SuppressWarnings("unchecked") - Consumer authenticationValidator = mock(Consumer.class); - this.authenticationProvider.setAuthenticationValidator(authenticationValidator); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; - OAuth2PushedAuthorizationRequestAuthenticationToken authentication = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), clientPrincipal, redirectUri, STATE, - registeredClient.getScopes(), null); - OAuth2PushedAuthorizationRequestAuthenticationToken authenticationResult = (OAuth2PushedAuthorizationRequestAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertPushedAuthorizationResponse(registeredClient, authentication, authenticationResult); - verify(authenticationValidator).accept(any()); - } - - private void assertPushedAuthorizationResponse(RegisteredClient registeredClient, - OAuth2PushedAuthorizationRequestAuthenticationToken authentication, - OAuth2PushedAuthorizationRequestAuthenticationToken authenticationResult) { - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization authorization = authorizationCaptor.getValue(); - - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - assertThat(authorizationRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorizationRequest.getResponseType()).isEqualTo(OAuth2AuthorizationResponseType.CODE); - assertThat(authorizationRequest.getAuthorizationUri()).isEqualTo(authentication.getAuthorizationUri()); - assertThat(authorizationRequest.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authorizationRequest.getRedirectUri()).isEqualTo(authentication.getRedirectUri()); - assertThat(authorizationRequest.getScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorizationRequest.getState()).isEqualTo(authentication.getState()); - assertThat(authorizationRequest.getAdditionalParameters()).isEqualTo(authentication.getAdditionalParameters()); - - assertThat(authorization.getRegisteredClientId()).isEqualTo(registeredClient.getId()); - assertThat(authorization.getPrincipalName()).isEqualTo(authentication.getName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(authorization.getAttribute(OAuth2ParameterNames.STATE)).isNotNull(); - - assertThat(authenticationResult.getClientId()).isEqualTo(authorizationRequest.getClientId()); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri()); - assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri()); - assertThat(authenticationResult.getScopes()).isEqualTo(authorizationRequest.getScopes()); - assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState()); - assertThat(authenticationResult.getRequestUri()).isNotNull(); - assertThat(authenticationResult.getRequestUriExpiresAt()).isNotNull(); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - } - - private static void assertAuthenticationException( - OAuth2AuthorizationCodeRequestAuthenticationException authenticationException, String errorCode, - String parameterName, String redirectUri) { - - OAuth2Error error = authenticationException.getError(); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).contains(parameterName); - - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationException - .getAuthorizationCodeRequestAuthentication(); - assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java deleted file mode 100644 index 795328c3d..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java +++ /dev/null @@ -1,690 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2RefreshTokenAuthenticationProvider}. - * - * @author Alexey Nesterov - * @author Joe Grandja - * @author Anoop Garlapati - * @since 0.0.3 - */ -public class OAuth2RefreshTokenAuthenticationProviderTests { - - private OAuth2AuthorizationService authorizationService; - - private JwtEncoder jwtEncoder; - - private OAuth2TokenCustomizer jwtCustomizer; - - private OAuth2TokenCustomizer accessTokenCustomizer; - - private OAuth2TokenGenerator tokenGenerator; - - private JwtEncoder dPoPProofJwtEncoder; - - private OAuth2RefreshTokenAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.jwtEncoder = mock(JwtEncoder.class); - given(this.jwtEncoder.encode(any())).willReturn(createJwt(Collections.singleton("scope1"))); - this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); - JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder); - jwtGenerator.setJwtCustomizer(this.jwtCustomizer); - this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); - accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator(jwtGenerator, - accessTokenGenerator, refreshTokenGenerator); - this.tokenGenerator = spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - this.dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - this.authenticationProvider = new OAuth2RefreshTokenAuthenticationProvider(this.authorizationService, - this.tokenGenerator); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationProvider(null, this.tokenGenerator)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("authorizationService cannot be null"); - } - - @Test - public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationProvider(this.authorizationService, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenGenerator cannot be null"); - } - - @Test - public void supportsWhenSupportedAuthenticationThenTrue() { - assertThat(this.authenticationProvider.supports(OAuth2RefreshTokenAuthenticationToken.class)).isTrue(); - } - - @Test - public void supportsWhenUnsupportedAuthenticationThenFalse() { - assertThat(this.authenticationProvider.supports(OAuth2ClientCredentialsAuthenticationToken.class)).isFalse(); - } - - @Test - public void authenticateWhenValidRefreshTokenThenReturnAccessToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - Map additionalParameters = new HashMap<>(); - additionalParameters.put("dpop_proof", generateDPoPProof("http://localhost/oauth2/token")); - additionalParameters.put("dpop_method", "POST"); - additionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, - additionalParameters); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(jwtEncodingContext.getPrincipal()) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(authorization); - assertThat(jwtEncodingContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(jwtEncodingContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(jwtEncodingContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(jwtEncodingContext.getJwsHeader()).isNotNull(); - assertThat(jwtEncodingContext.getClaims()).isNotNull(); - assertThat(jwtEncodingContext.get(OAuth2TokenContext.DPOP_PROOF_KEY)).isNotNull(); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRegisteredClient().getId()) - .isEqualTo(updatedAuthorization.getRegisteredClientId()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - assertThat(updatedAuthorization.getAccessToken()).isNotEqualTo(authorization.getAccessToken()); - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - // By default, refresh token is reused - assertThat(updatedAuthorization.getRefreshToken()).isEqualTo(authorization.getRefreshToken()); - } - - @Test - public void authenticateWhenValidRefreshTokenThenReturnIdToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OidcIdToken authorizedIdToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject("subject") - .issuedAt(Instant.now()) - .expiresAt(Instant.now().plusSeconds(60)) - .claim("sid", "sessionId-1234") - .claim(IdTokenClaimNames.AUTH_TIME, Date.from(Instant.now())) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(authorizedIdToken) - .build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer, times(2)).customize(jwtEncodingContextCaptor.capture()); - // Access Token context - JwtEncodingContext accessTokenContext = jwtEncodingContextCaptor.getAllValues().get(0); - assertThat(accessTokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(accessTokenContext.getPrincipal()) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(accessTokenContext.getAuthorization()).isEqualTo(authorization); - assertThat(accessTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(accessTokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(accessTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(accessTokenContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(accessTokenContext.getJwsHeader()).isNotNull(); - assertThat(accessTokenContext.getClaims()).isNotNull(); - Map claims = new HashMap<>(); - accessTokenContext.getClaims().claims(claims::putAll); - assertThat(claims).flatExtracting(OAuth2ParameterNames.SCOPE) - .containsExactlyInAnyOrder(OidcScopes.OPENID, "scope1"); - // ID Token context - JwtEncodingContext idTokenContext = jwtEncodingContextCaptor.getAllValues().get(1); - assertThat(idTokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(idTokenContext.getPrincipal()) - .isEqualTo(authorization.getAttribute(Principal.class.getName())); - assertThat(idTokenContext.getAuthorization()).isNotEqualTo(authorization); - assertThat(idTokenContext.getAuthorization().getAccessToken()).isNotEqualTo(authorization.getAccessToken()); - assertThat(idTokenContext.getAuthorizedScopes()).isEqualTo(authorization.getAuthorizedScopes()); - assertThat(idTokenContext.getTokenType().getValue()).isEqualTo(OidcParameterNames.ID_TOKEN); - assertThat(idTokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(idTokenContext.getAuthorizationGrant()) - .isEqualTo(authentication); - assertThat(idTokenContext.getJwsHeader()).isNotNull(); - assertThat(idTokenContext.getClaims()).isNotNull(); - - verify(this.jwtEncoder, times(2)).encode(any()); // Access token and ID Token - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRegisteredClient().getId()) - .isEqualTo(updatedAuthorization.getRegisteredClientId()); - assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(accessTokenAuthentication.getAccessToken()) - .isEqualTo(updatedAuthorization.getAccessToken().getToken()); - assertThat(updatedAuthorization.getAccessToken()).isNotEqualTo(authorization.getAccessToken()); - OAuth2Authorization.Token idToken = updatedAuthorization.getToken(OidcIdToken.class); - assertThat(idToken).isNotNull(); - assertThat(accessTokenAuthentication.getAdditionalParameters()) - .containsExactly(entry(OidcParameterNames.ID_TOKEN, idToken.getToken().getTokenValue())); - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - // By default, refresh token is reused - assertThat(updatedAuthorization.getRefreshToken()).isEqualTo(authorization.getRefreshToken()); - } - - @Test - public void authenticateWhenReuseRefreshTokensFalseThenReturnNewRefreshToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(TokenSettings.builder().reuseRefreshTokens(false).build()) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - - assertThat(accessTokenAuthentication.getRefreshToken()) - .isEqualTo(updatedAuthorization.getRefreshToken().getToken()); - assertThat(updatedAuthorization.getRefreshToken()).isNotEqualTo(authorization.getRefreshToken()); - - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.tokenGenerator, times(2)).generate(tokenContextCaptor.capture()); - // tokenGenerator is first invoked for generating a new access token and then for - // generating the refresh token - List tokenContexts = tokenContextCaptor.getAllValues(); - assertThat(tokenContexts).hasSize(2); - assertThat(tokenContexts.get(0).getAuthorization().getAccessToken().getToken().getTokenValue()) - .isEqualTo("access-token"); - assertThat(tokenContexts.get(1).getAuthorization().getAccessToken().getToken().getTokenValue()) - .isEqualTo("refreshed-access-token"); - } - - @Test - public void authenticateWhenRequestedScopesAuthorizedThenAccessTokenIncludesScopes() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope("scope2") - .scope("scope3") - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - Set authorizedScopes = authorization.getAuthorizedScopes(); - Set requestedScopes = new HashSet<>(authorizedScopes); - requestedScopes.remove("scope1"); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null); - - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(requestedScopes); - } - - @Test - public void authenticateWhenRequestedScopesNotAuthorizedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - Set authorizedScopes = authorization.getAuthorizedScopes(); - Set requestedScopes = new HashSet<>(authorizedScopes); - requestedScopes.add("unauthorized"); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - } - - @Test - public void authenticateWhenInvalidRefreshTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken("invalid", - clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(registeredClient.getClientId(), - registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - "refresh-token", clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - "refresh-token", clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenRefreshTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient2, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient2.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenClientNotAuthorizedToRefreshTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantTypes((grantTypes) -> grantTypes.remove(AuthorizationGrantType.REFRESH_TOKEN)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - } - - @Test - public void authenticateWhenExpiredRefreshTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2RefreshToken expiredRefreshToken = new OAuth2RefreshToken("expired-refresh-token", - Instant.now().minusSeconds(120), Instant.now().minusSeconds(60)); - authorization = OAuth2Authorization.from(authorization).token(expiredRefreshToken).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenRevokedRefreshTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now().minusSeconds(120), - Instant.now().plusSeconds(1000)); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(refreshToken, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)) - .build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - @Test - public void authenticateWhenAccessTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - return null; - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()).contains("The token generator failed to generate the access token."); - }); - } - - @Test - public void authenticateWhenRefreshTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(TokenSettings.builder().reuseRefreshTokens(false).build()) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { - return null; - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()) - .contains("The token generator failed to generate the refresh token."); - }); - } - - @Test - public void authenticateWhenIdTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - willAnswer((answer) -> { - OAuth2TokenContext context = answer.getArgument(0); - if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { - return null; - } - else { - return answer.callRealMethod(); - } - }).given(this.tokenGenerator).generate(any()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()).contains("The token generator failed to generate the ID token."); - }); - } - - @Test - public void authenticateWhenAccessTokenFormatReferenceThenAccessTokenGeneratorCalled() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build()) - .build(); - // @formatter:on - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - eq(OAuth2TokenType.REFRESH_TOKEN))) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null); - - this.authenticationProvider.authenticate(authentication); - - verify(this.accessTokenCustomizer).customize(any()); - } - - private static Jwt createJwt(Set scope) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return Jwt.withTokenValue("refreshed-access-token") - .header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName()) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .claim(OAuth2ParameterNames.SCOPE, scope) - .build(); - } - - private String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = this.dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java deleted file mode 100644 index 99c89f9f9..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2RefreshTokenAuthenticationToken}. - * - * @author Alexey Nesterov - * @since 0.0.3 - */ -public class OAuth2RefreshTokenAuthenticationTokenTests { - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private Set scopes = Collections.singleton("scope1"); - - private Map additionalParameters = Collections.singletonMap("param1", "value1"); - - @Test - public void constructorWhenRefreshTokenNullOrEmptyThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, this.clientPrincipal, this.scopes, - this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("refreshToken cannot be empty"); - assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", this.clientPrincipal, this.scopes, - this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("refreshToken cannot be empty"); - } - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("refresh-token", null, this.scopes, - this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenScopesProvidedThenCreated() { - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - "refresh-token", this.clientPrincipal, this.scopes, this.additionalParameters); - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(authentication.getRefreshToken()).isEqualTo("refresh-token"); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getScopes()).isEqualTo(this.scopes); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java deleted file mode 100644 index 34784859a..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OAuth2TokenExchangeActor}. - * - * @author Steve Riesenberg - */ -public class OAuth2TokenExchangeActorTests { - - @Test - public void constructorWhenClaimsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2TokenExchangeActor(null)) - .withMessage("claims cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenRequiredParametersThenCreated() { - Map claims = Map.of("claim1", "value1"); - OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(claims); - assertThat(actor.getClaims()).isEqualTo(claims); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java deleted file mode 100644 index 8e8001d4c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; - -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.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests for {@link OAuth2TokenExchangeAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OAuth2TokenExchangeAuthenticationProviderTests { - - private static final Set RESOURCES = Set.of("https://mydomain.com/resource1", - "https://mydomain.com/resource2"); - - private static final Set AUDIENCES = Set.of("audience1", "audience2"); - - private static final String SUBJECT_TOKEN = "EfYu_0jEL"; - - private static final String ACTOR_TOKEN = "JlNE_xR1f"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2TokenGenerator tokenGenerator; - - private JwtEncoder dPoPProofJwtEncoder; - - private OAuth2TokenExchangeAuthenticationProvider authenticationProvider; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.tokenGenerator = mock(OAuth2TokenGenerator.class); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - this.dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - this.authenticationProvider = new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService, - this.tokenGenerator); - mockAuthorizationServerContext(); - } - - @AfterEach - public void tearDown() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(null, this.tokenGenerator)) - .withMessage("authorizationService cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService, null)) - .withMessage("tokenGenerator cannot be null"); - // @formatter:on - } - - @Test - public void supportsWhenTypeOAuth2TokenExchangeAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2TokenExchangeAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken("client-1", - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null); - Authentication authentication = new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, - ACCESS_TOKEN_TYPE_VALUE, clientPrincipal, null, null, RESOURCES, AUDIENCES, null, null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenInvalidGrantTypeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); - // @formatter:on - } - - @Test - public void authenticateWhenInvalidRequestedTokenTypeThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build()) - .build(); - // @formatter:on - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void authenticateWhenSubjectTokenNotFoundThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenSubjectTokenNotActiveThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createExpiredAccessToken(SUBJECT_TOKEN)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenSubjectTokenTypeJwtAndSubjectTokenFormatReferenceThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenSubjectPrincipalNullThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - // @formatter:off - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .attributes((attributes) -> attributes.remove(Principal.class.getName())) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).willReturn(authorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenActorTokenNotFoundThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, (OAuth2Authorization) null); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenActorTokenNotActiveThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createExpiredAccessToken(ACTOR_TOKEN)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenActorTokenTypeJwtAndActorTokenFormatReferenceThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(ACTOR_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenMayActAndActorIssClaimNotAuthorizedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - Map authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", - OAuth2TokenClaimNames.SUB, "actor"); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) - .build(); - // @formatter:on - Map actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "invalid-issuer", - OAuth2TokenClaimNames.SUB, "actor"); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenMayActAndActorSubClaimNotAuthorizedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - Map authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", - OAuth2TokenClaimNames.SUB, "actor"); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) - .build(); - // @formatter:on - Map actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", OAuth2TokenClaimNames.SUB, - "invalid-actor"); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenMayActAndImpersonationThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient); - Map authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", - OAuth2TokenClaimNames.SUB, "actor"); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims))) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenInvalidScopeInRequestThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, - Set.of("invalid")); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(ACTOR_TOKEN)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenInvalidScopeInSubjectAuthorizationThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, Set.of()); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .authorizedScopes(Set.of("invalid")) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(ACTOR_TOKEN)) - .build(); - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE); - // @formatter:on - - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verifyNoMoreInteractions(this.authorizationService); - verifyNoInteractions(this.tokenGenerator); - } - - @Test - public void authenticateWhenNoActorTokenAndValidTokenExchangeThenReturnAccessTokenForImpersonation() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - Map additionalParameters = new HashMap<>(); - additionalParameters.put("dpop_proof", generateDPoPProof("http://localhost/oauth2/token")); - additionalParameters.put("dpop_method", "POST"); - additionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken( - JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, clientPrincipal, null, null, RESOURCES, - AUDIENCES, registeredClient.getScopes(), additionalParameters); - TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization); - OAuth2AccessToken accessToken = createAccessToken("token-value"); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(accessToken); - OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); - assertThat(authenticationResult.getRefreshToken()).isNull(); - assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); - assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) - .isEqualTo(JWT_TOKEN_TYPE_VALUE); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); - assertThat(tokenContext.getPrincipal()).isSameAs(userPrincipal); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(tokenContext.get(OAuth2TokenContext.DPOP_PROOF_KEY)).isNotNull(); - - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorization.getAttribute(Principal.class.getName())).isSameAs(userPrincipal); - assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); - assertThat(authorization.getRefreshToken()).isNull(); - } - - @Test - public void authenticateWhenNoActorTokenAndPreviousActorThenReturnAccessTokenForImpersonation() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient); - TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); - OAuth2TokenExchangeActor previousActor = new OAuth2TokenExchangeActor( - Map.of(OAuth2TokenClaimNames.ISS, "issuer1", OAuth2TokenClaimNames.SUB, "actor")); - OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = new OAuth2TokenExchangeCompositeAuthenticationToken( - userPrincipal, List.of(previousActor)); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN)) - .attribute(Principal.class.getName(), subjectPrincipal) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization); - OAuth2AccessToken accessToken = createAccessToken("token-value"); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(accessToken); - OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); - assertThat(authenticationResult.getRefreshToken()).isNull(); - assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); - assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) - .isEqualTo(JWT_TOKEN_TYPE_VALUE); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); - assertThat(tokenContext.getPrincipal()).isSameAs(userPrincipal); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorization.getAttribute(Principal.class.getName())).isSameAs(userPrincipal); - assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); - assertThat(authorization.getRefreshToken()).isNull(); - } - - @Test - public void authenticateWhenActorTokenAndValidTokenExchangeThenReturnAccessTokenForDelegation() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient); - TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER"); - OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor( - Map.of(OAuth2TokenClaimNames.ISS, "issuer1", OAuth2TokenClaimNames.SUB, "actor1")); - OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor( - Map.of(OAuth2TokenClaimNames.ISS, "issuer2", OAuth2TokenClaimNames.SUB, "actor2")); - OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = new OAuth2TokenExchangeCompositeAuthenticationToken( - userPrincipal, List.of(actor1)); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", actor2.getClaims()))) - .attribute(Principal.class.getName(), subjectPrincipal) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(actor2.getSubject()) - .token(createAccessToken(ACTOR_TOKEN), withClaims(actor2.getClaims())) - .build(); - // @formatter:on - given(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))) - .willReturn(subjectAuthorization, actorAuthorization); - OAuth2AccessToken accessToken = createAccessToken("token-value"); - given(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).willReturn(accessToken); - OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal()); - assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken); - assertThat(authenticationResult.getRefreshToken()).isNull(); - assertThat(authenticationResult.getAdditionalParameters()).hasSize(1); - assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE)) - .isEqualTo(JWT_TOKEN_TYPE_VALUE); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN); - verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); - verify(this.authorizationService).save(authorizationCaptor.capture()); - verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator); - - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization); - assertThat(tokenContext.getAuthorizationServerContext()).isNotNull(); - assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(tokenContext.getAuthorizationGrant()).isEqualTo(authentication); - assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - - OAuth2TokenExchangeCompositeAuthenticationToken tokenContextPrincipal = tokenContext.getPrincipal(); - assertThat(tokenContextPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject()); - assertThat(tokenContextPrincipal.getActors()).containsExactly(actor2, actor1); - - OAuth2Authorization authorization = authorizationCaptor.getValue(); - assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName()); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes()); - assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken); - assertThat(authorization.getRefreshToken()).isNull(); - - OAuth2TokenExchangeCompositeAuthenticationToken authorizationPrincipal = authorization - .getAttribute(Principal.class.getName()); - assertThat(authorizationPrincipal).isNotNull(); - assertThat(authorizationPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject()); - assertThat(authorizationPrincipal.getActors()).containsExactly(actor2, actor1); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> "https://provider.com"); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient) { - return createDelegationRequest(registeredClient, registeredClient.getScopes()); - } - - private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient, - Set requestedScopes) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, - clientPrincipal, ACTOR_TOKEN, ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, requestedScopes, null); - } - - private static OAuth2TokenExchangeAuthenticationToken createImpersonationRequest( - RegisteredClient registeredClient) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, - clientPrincipal, null, null, RESOURCES, AUDIENCES, registeredClient.getScopes(), null); - } - - private static OAuth2TokenExchangeAuthenticationToken createJwtRequest(RegisteredClient registeredClient) { - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null); - return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, JWT_TOKEN_TYPE_VALUE, - clientPrincipal, ACTOR_TOKEN, JWT_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, registeredClient.getScopes(), - null); - } - - private static OAuth2AccessToken createAccessToken(String tokenValue) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); - } - - private static OAuth2AccessToken createExpiredAccessToken(String tokenValue) { - Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES); - Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); - } - - private static Consumer> withClaims(Map claims) { - return (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claims); - } - - private static Consumer> withTokenFormat(OAuth2TokenFormat tokenFormat) { - return (metadata) -> metadata.put(OAuth2TokenFormat.class.getName(), tokenFormat.getValue()); - } - - private String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = this.dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java deleted file mode 100644 index cd45aefe3..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenExchangeAuthenticationToken}. - * - * @author Steve Riesenberg - */ -public class OAuth2TokenExchangeAuthenticationTokenTests { - - private static final Set RESOURCES = Set.of("https://mydomain.com/resource1", - "https://mydomain.com/resource2"); - - private static final Set AUDIENCES = Set.of("audience1", "audience2"); - - private static final String SUBJECT_TOKEN = "EfYu_0jEL"; - - private static final String ACTOR_TOKEN = "JlNE_xR1f"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private Set scopes = Collections.singleton("scope1"); - - private Map additionalParameters = Collections.singletonMap("param1", "value1"); - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, null, null, null, null, null, null, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenRequestedTokenTypeNullOrEmptyThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("requestedTokenType cannot be empty"); - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken("", null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("requestedTokenType cannot be empty"); - // @formatter:on - } - - @Test - public void constructorWhenSubjectTokenNullOrEmptyThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, null, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("subjectToken cannot be empty"); - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, "", null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("subjectToken cannot be empty"); - // @formatter:on - } - - @Test - public void constructorWhenSubjectTokenTypeNullOrEmptyThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("subjectTokenType cannot be empty"); - assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, "", this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("subjectTokenType cannot be empty"); - // @formatter:on - } - - @Test - public void constructorWhenRequiredParametersProvidedThenCreated() { - OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken( - JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, null, null, null, - null, null, this.additionalParameters); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); - assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); - assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(authentication.getActorToken()).isNull(); - assertThat(authentication.getActorTokenType()).isNull(); - assertThat(authentication.getResources()).isEmpty(); - assertThat(authentication.getAudiences()).isEmpty(); - assertThat(authentication.getScopes()).isEmpty(); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - - @Test - public void constructorWhenAllParametersProvidedThenCreated() { - OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken( - JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, ACTOR_TOKEN, - ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, this.scopes, this.additionalParameters); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); - assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); - assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN); - assertThat(authentication.getActorTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(authentication.getResources()).isEqualTo(RESOURCES); - assertThat(authentication.getAudiences()).isEqualTo(AUDIENCES); - assertThat(authentication.getScopes()).isEqualTo(this.scopes); - assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java deleted file mode 100644 index f4ef7b90d..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OAuth2TokenExchangeCompositeAuthenticationToken}. - * - * @author Steve Riesenberg - */ -public class OAuth2TokenExchangeCompositeAuthenticationTokenTests { - - @Test - public void constructorWhenSubjectNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(null, null)) - .withMessage("subject cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenActorsNullThenThrowIllegalArgumentException() { - TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null); - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(subject, null)) - .withMessage("actors cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenRequiredParametersProvidedThenCreated() { - TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null); - OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of("claim1", "value1")); - OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of("claim2", "value2")); - List actors = List.of(actor1, actor2); - OAuth2TokenExchangeCompositeAuthenticationToken authentication = new OAuth2TokenExchangeCompositeAuthenticationToken( - subject, actors); - assertThat(authentication.getSubject()).isEqualTo(subject); - assertThat(authentication.getActors()).isEqualTo(actors); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java deleted file mode 100644 index 99c27fbeb..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2TokenIntrospectionAuthenticationProvider}. - * - * @author Gerardo Roza - * @author Joe Grandja - */ -public class OAuth2TokenIntrospectionAuthenticationProviderTests { - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private OAuth2TokenIntrospectionAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new OAuth2TokenIntrospectionAuthenticationProvider( - this.registeredClientRepository, this.authorizationService); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationProvider(null, this.authorizationService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2TokenIntrospectionAuthenticationProvider(this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2TokenIntrospectionAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2TokenIntrospectionAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(registeredClient.getClientId(), - registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - "token", clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - "token", clientPrincipal, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenInvalidTokenThenNotActive() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - "token", clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - assertThat(authenticationResult.isAuthenticated()).isFalse(); - assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1); - assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); - } - - @Test - public void authenticateWhenTokenInvalidatedThenNotActive() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - authorization = OAuth2Authorization.from(authorization).invalidate(accessToken).build(); - given(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())) - .willReturn(authorization); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1); - assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); - } - - @Test - public void authenticateWhenTokenExpiredThenNotActive() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Instant issuedAt = Instant.now().minus(Duration.ofHours(1)); - Instant expiresAt = Instant.now().minus(Duration.ofMinutes(1)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - issuedAt, expiresAt); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(accessToken) - .build(); - given(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())) - .willReturn(authorization); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1); - assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); - } - - @Test - public void authenticateWhenTokenBeforeUseThenNotActive() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Instant issuedAt = Instant.now(); - Instant notBefore = issuedAt.plus(Duration.ofMinutes(5)); - Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - issuedAt, expiresAt); - Map accessTokenClaims = Collections.singletonMap(OAuth2TokenClaimNames.NBF, notBefore); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, accessToken, accessTokenClaims) - .build(); - given(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())) - .willReturn(authorization); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1); - assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); - } - - @Test - public void authenticateWhenValidAccessTokenThenActive() { - RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build(); - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - issuedAt, expiresAt, new HashSet<>(Arrays.asList("scope1", "scope2"))); - - // @formatter:off - OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder() - .issuer("https://provider.com") - .subject("subject") - .audience(Collections.singletonList(authorizedClient.getClientId())) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .id("id") - .claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes()) - .claim("custom-claim", "custom-value") - .build(); - // @formatter:on - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(authorizedClient, accessToken, claimsSet.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).willReturn(authorizedClient); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - verify(this.registeredClientRepository).findById(eq(authorizedClient.getId())); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims(); - assertThat(tokenClaims.isActive()).isTrue(); - assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId()); - assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt()); - assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt()); - assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); - assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore()); - assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject()); - assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience()); - assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer()); - assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId()); - assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes()); - assertThat(tokenClaims.getClaim("custom-claim")).isEqualTo("custom-value"); - } - - @Test - public void authenticateWhenValidRefreshTokenThenActive() { - RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build(); - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - given(this.authorizationService.findByToken(eq(refreshToken.getTokenValue()), isNull())) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).willReturn(authorizedClient); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - refreshToken.getTokenValue(), clientPrincipal, null, null); - OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); - verify(this.registeredClientRepository).findById(eq(authorizedClient.getId())); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims(); - assertThat(tokenClaims.getClaims()).hasSize(4); - assertThat(tokenClaims.isActive()).isTrue(); - assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId()); - assertThat(tokenClaims.getIssuedAt()).isEqualTo(refreshToken.getIssuedAt()); - assertThat(tokenClaims.getExpiresAt()).isEqualTo(refreshToken.getExpiresAt()); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationTokenTests.java deleted file mode 100644 index 1fa87c76f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationTokenTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenIntrospectionAuthenticationToken}. - * - * @author Gerardo Roza - * @author Joe Grandja - */ -public class OAuth2TokenIntrospectionAuthenticationTokenTests { - - private String token = "token"; - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true).build(); - - @Test - public void constructorWhenTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.clientPrincipal, null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("token cannot be empty"); - } - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, null, null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenAuthenticatedAndTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.clientPrincipal, this.tokenClaims)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("token cannot be empty"); - } - - @Test - public void constructorWhenAuthenticatedAndClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, null, this.tokenClaims)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenAuthenticatedAndTokenClaimsNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, this.clientPrincipal, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenClaims cannot be null"); - } - - @Test - public void constructorWhenTokenProvidedThenCreated() { - Map additionalParameters = Collections.singletonMap("custom-param", "custom-value"); - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - this.token, this.clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), additionalParameters); - assertThat(authentication.getToken()).isEqualTo(this.token); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getTokenTypeHint()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN.getValue()); - assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters); - assertThat(authentication.getTokenClaims()).isNotNull(); - assertThat(authentication.getTokenClaims().isActive()).isFalse(); - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - public void constructorWhenTokenClaimsProvidedThenCreated() { - OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( - this.token, this.clientPrincipal, this.tokenClaims); - assertThat(authentication.getToken()).isEqualTo(this.token); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getTokenTypeHint()).isNull(); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - assertThat(authentication.getTokenClaims()).isEqualTo(this.tokenClaims); - assertThat(authentication.isAuthenticated()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java deleted file mode 100644 index 27f777480..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2TokenRevocationAuthenticationProvider}. - * - * @author Vivek Babu - * @author Joe Grandja - */ -public class OAuth2TokenRevocationAuthenticationProviderTests { - - private OAuth2AuthorizationService authorizationService; - - private OAuth2TokenRevocationAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new OAuth2TokenRevocationAuthenticationProvider(this.authorizationService); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationProvider(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2TokenRevocationAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2TokenRevocationAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(registeredClient.getClientId(), - registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken("token", - clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue()); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - registeredClient.getClientSecret(), null); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken("token", - clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue()); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenInvalidTokenThenNotRevoked() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken("token", - clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue()); - OAuth2TokenRevocationAuthenticationToken authenticationResult = (OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isFalse(); - verify(this.authorizationService, never()).save(any()); - } - - @Test - public void authenticateWhenTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(TestRegisteredClients.registeredClient2().build()) - .build(); - given(this.authorizationService.findByToken(eq("token"), isNull())).willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken("token", - clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void authenticateWhenValidRefreshTokenThenRevoked() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getRefreshToken().getToken().getTokenValue()), - isNull())) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken( - authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, - OAuth2TokenType.REFRESH_TOKEN.getValue()); - - OAuth2TokenRevocationAuthenticationToken authenticationResult = (OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - } - - @Test - public void authenticateWhenValidAccessTokenThenRevoked() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(authorization.getAccessToken().getToken().getTokenValue()), - isNull())) - .willReturn(authorization); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken( - authorization.getAccessToken().getToken().getTokenValue(), clientPrincipal, - OAuth2TokenType.ACCESS_TOKEN.getValue()); - - OAuth2TokenRevocationAuthenticationToken authenticationResult = (OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - verify(this.authorizationService).save(authorizationCaptor.capture()); - - OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue(); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isFalse(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationTokenTests.java deleted file mode 100644 index a87f6b642..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationTokenTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.time.Duration; -import java.time.Instant; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenRevocationAuthenticationToken}. - * - * @author Vivek Babu - * @author Joe Grandja - */ -public class OAuth2TokenRevocationAuthenticationTokenTests { - - private String token = "token"; - - private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret()); - - private String tokenTypeHint = OAuth2TokenType.ACCESS_TOKEN.getValue(); - - private OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, this.token, - Instant.now(), Instant.now().plus(Duration.ofHours(1))); - - @Test - public void constructorWhenTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy( - () -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal, this.tokenTypeHint)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("token cannot be empty"); - } - - @Test - public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.token, null, this.tokenTypeHint)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenRevokedTokenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("revokedToken cannot be null"); - } - - @Test - public void constructorWhenRevokedTokenAndClientPrincipalNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.accessToken, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("clientPrincipal cannot be null"); - } - - @Test - public void constructorWhenTokenProvidedThenCreated() { - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken( - this.token, this.clientPrincipal, this.tokenTypeHint); - assertThat(authentication.getToken()).isEqualTo(this.token); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getTokenTypeHint()).isEqualTo(this.tokenTypeHint); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - public void constructorWhenRevokedTokenProvidedThenCreated() { - OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken( - this.accessToken, this.clientPrincipal); - assertThat(authentication.getToken()).isEqualTo(this.accessToken.getTokenValue()); - assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal); - assertThat(authentication.getTokenTypeHint()).isNull(); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.isAuthenticated()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProviderTests.java deleted file mode 100644 index 36095a7d9..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProviderTests.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.authentication; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link PublicClientAuthenticationProvider}. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class PublicClientAuthenticationProviderTests { - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORIZATION_CODE = "code"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private PublicClientAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new PublicClientAuthenticationProvider(this.registeredClientRepository, - this.authorizationService); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new PublicClientAuthenticationProvider(null, this.authorizationService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new PublicClientAuthenticationProvider(this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId() + "-invalid", ClientAuthenticationMethod.NONE, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID); - }); - } - - @Test - public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("authentication_method"); - }); - } - - @Test - public void authenticateWhenInvalidCodeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - parameters.put(OAuth2ParameterNames.CODE, "invalid-code"); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CODE); - }); - } - - @Test - public void authenticateWhenMissingCodeChallengeThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(PkceParameterNames.CODE_CHALLENGE); - }); - } - - @Test - public void authenticateWhenMissingCodeVerifierThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createAuthorizationCodeTokenParameters(); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(PkceParameterNames.CODE_VERIFIER); - }); - } - - @Test - public void authenticateWhenS256MethodAndInvalidCodeVerifierThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters("invalid-code-verifier"); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - assertThat(error.getDescription()).contains(PkceParameterNames.CODE_VERIFIER); - }); - } - - @Test - public void authenticateWhenS256MethodAndValidCodeVerifierThenAuthenticated() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()).isNull(); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - } - - @Test - public void authenticateWhenUnsupportedCodeChallengeMethodThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - Map authorizationRequestAdditionalParameters = createPkceAuthorizationParametersS256(); - // This should never happen: the Authorization endpoint should not allow it - authorizationRequestAdditionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, - "unsupported-challenge-method"); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, authorizationRequestAdditionalParameters) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.NONE, null, parameters); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_GRANT); - } - - private static Map createAuthorizationCodeTokenParameters() { - Map parameters = new HashMap<>(); - parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.put(OAuth2ParameterNames.CODE, AUTHORIZATION_CODE); - return parameters; - } - - private static Map createPkceTokenParameters(String codeVerifier) { - Map parameters = createAuthorizationCodeTokenParameters(); - parameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); - return parameters; - } - - private static Map createPkceAuthorizationParametersS256() { - Map parameters = new HashMap<>(); - parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - parameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - return parameters; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProviderTests.java deleted file mode 100644 index b144fc208..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProviderTests.java +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.authentication; - -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.util.Base64; -import okhttp3.mockwebserver.Dispatcher; -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.MediaType; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link X509ClientCertificateAuthenticationProvider}. - * - * @author Joe Grandja - */ -public class X509ClientCertificateAuthenticationProviderTests { - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORIZATION_CODE = "code"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private JWKSet selfSignedCertificateJwkSet; - - private MockWebServer server; - - private String clientJwkSetUrl; - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private X509ClientCertificateAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() throws Exception { - // @formatter:off - X509Certificate selfSignedCertificate = TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE[0]; - RSAKey selfSignedRSAKey = new RSAKey.Builder((RSAPublicKey) selfSignedCertificate.getPublicKey()) - .keyUse(KeyUse.SIGNATURE) - .keyID(UUID.randomUUID().toString()) - .x509CertChain(Collections.singletonList(Base64.encode(selfSignedCertificate.getEncoded()))) - .build(); - // @formatter:on - this.selfSignedCertificateJwkSet = new JWKSet(selfSignedRSAKey); - this.server = new MockWebServer(); - this.server.start(); - this.clientJwkSetUrl = this.server.url("/jwks").toString(); - // @formatter:off - MockResponse response = new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(this.selfSignedCertificateJwkSet.toString()); - // @formatter:on - this.server.enqueue(response); - - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new X509ClientCertificateAuthenticationProvider(this.registeredClientRepository, - this.authorizationService); - } - - @AfterEach - public void tearDown() throws Exception { - this.server.shutdown(); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new X509ClientCertificateAuthenticationProvider(null, this.authorizationService)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new X509ClientCertificateAuthenticationProvider(this.registeredClientRepository, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationService cannot be null"); - } - - @Test - public void setCertificateVerifierWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setCertificateVerifier(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("certificateVerifier cannot be null"); - } - - @Test - public void supportsWhenTypeOAuth2ClientAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OAuth2ClientAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId() + "-invalid", ClientAuthenticationMethod.TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID); - }); - } - - @Test - public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("authentication_method"); - }); - } - - @Test - public void authenticateWhenX509CertificateNotProvidedThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, null, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("credentials"); - }); - } - - @Test - public void authenticateWhenPKIX509CertificateInvalidSubjectDNThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN("CN=demo-client-sample-2,OU=Spring Samples,O=Spring,C=US") - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("x509_certificate_subject_dn"); - }); - } - - @Test - public void authenticateWhenPKIX509CertificateValidThenAuthenticated() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); - - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateInvalidIssuerThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl(this.clientJwkSetUrl) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - // PKI Certificate will have different issuer - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("x509_certificate_issuer"); - }); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateMissingClientJwkSetUrlThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("client_jwk_set_url"); - }); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateInvalidClientJwkSetUrlThenThrowOAuth2AuthenticationException() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl("https://this is an invalid URL") - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains("jwk_set_uri"); - }); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateJwkSetResponseErrorStatusThenThrowOAuth2AuthenticationException() { - MockResponse jwkSetResponse = new MockResponse().setResponseCode(400); - authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidThenThrowOAuth2AuthenticationException( - jwkSetResponse, "jwk_set_response_error"); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidStatusThenThrowOAuth2AuthenticationException() { - MockResponse jwkSetResponse = new MockResponse().setResponseCode(204); - authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidThenThrowOAuth2AuthenticationException( - jwkSetResponse, "jwk_set_response_status"); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidContentThenThrowOAuth2AuthenticationException() { - MockResponse jwkSetResponse = new MockResponse().setResponseCode(200).setBody("invalid-content"); - authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidThenThrowOAuth2AuthenticationException( - jwkSetResponse, "jwk_set_response_body"); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateJwkSetResponseNoMatchingKeysThenThrowOAuth2AuthenticationException() - throws Exception { - // @formatter:off - X509Certificate pkiCertificate = TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0]; - RSAKey pkiRSAKey = new RSAKey.Builder((RSAPublicKey) pkiCertificate.getPublicKey()) - .keyUse(KeyUse.SIGNATURE) - .keyID(UUID.randomUUID().toString()) - .x509CertChain(Collections.singletonList(Base64.encode(pkiCertificate.getEncoded()))) - .build(); - // @formatter:on - - // @formatter:off - MockResponse jwkSetResponse = new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(new JWKSet(pkiRSAKey).toString()); - // @formatter:on - - authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidThenThrowOAuth2AuthenticationException( - jwkSetResponse, "x509_certificate"); - } - - private void authenticateWhenSelfSignedX509CertificateJwkSetResponseInvalidThenThrowOAuth2AuthenticationException( - final MockResponse jwkSetResponse, String expectedErrorDescription) { - - // @formatter:off - final Dispatcher dispatcher = new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - return jwkSetResponse; - } - }; - this.server.setDispatcher(dispatcher); - // @formatter:on - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl(this.clientJwkSetUrl) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null); - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - assertThat(error.getDescription()).contains(expectedErrorDescription); - }); - } - - @Test - public void authenticateWhenSelfSignedX509CertificateValidThenAuthenticated() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl(this.clientJwkSetUrl) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null); - - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()) - .isEqualTo(TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH); - } - - @Test - public void authenticateWhenPkceAndValidCodeVerifierThenAuthenticated() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .build(); - // @formatter:on - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, createPkceAuthorizationParametersS256()) - .build(); - given(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE))) - .willReturn(authorization); - - Map parameters = createPkceTokenParameters(S256_CODE_VERIFIER); - - OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken( - registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, parameters); - - OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(authenticationResult.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH); - } - - private static Map createPkceAuthorizationParametersS256() { - Map parameters = new HashMap<>(); - parameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - parameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - return parameters; - } - - private static Map createPkceTokenParameters(String codeVerifier) { - Map parameters = createAuthorizationCodeTokenParameters(); - parameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier); - return parameters; - } - - private static Map createAuthorizationCodeTokenParameters() { - Map parameters = new HashMap<>(); - parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.put(OAuth2ParameterNames.CODE, AUTHORIZATION_CODE); - return parameters; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepositoryTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepositoryTests.java deleted file mode 100644 index b17398ee9..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/InMemoryRegisteredClientRepositoryTests.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.client; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link InMemoryRegisteredClientRepository}. - * - * @author Anoop Garlapati - * @author Ovidiu Popa - * @author Joe Grandja - */ -public class InMemoryRegisteredClientRepositoryTests { - - private RegisteredClient registration = TestRegisteredClients.registeredClient().build(); - - private InMemoryRegisteredClientRepository clients = new InMemoryRegisteredClientRepository(this.registration); - - @Test - public void constructorVarargsRegisteredClientWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - RegisteredClient registration = null; - new InMemoryRegisteredClientRepository(registration); - }).withMessageContaining("registration cannot be null"); - } - - @Test - public void constructorListRegisteredClientWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - List registrations = null; - new InMemoryRegisteredClientRepository(registrations); - }).withMessageContaining("registrations cannot be empty"); - } - - @Test - public void constructorListRegisteredClientWhenEmptyThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - List registrations = Collections.emptyList(); - new InMemoryRegisteredClientRepository(registrations); - }).withMessageContaining("registrations cannot be empty"); - } - - @Test - public void constructorListRegisteredClientWhenDuplicateIdThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - RegisteredClient anotherRegistrationWithSameId = TestRegisteredClients.registeredClient2() - .id(this.registration.getId()) - .build(); - List registrations = Arrays.asList(this.registration, anotherRegistrationWithSameId); - new InMemoryRegisteredClientRepository(registrations); - }).withMessageStartingWith("Registered client must be unique. Found duplicate identifier:"); - } - - @Test - public void constructorListRegisteredClientWhenDuplicateClientIdThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - RegisteredClient anotherRegistrationWithSameClientId = TestRegisteredClients.registeredClient2() - .clientId(this.registration.getClientId()) - .build(); - List registrations = Arrays.asList(this.registration, - anotherRegistrationWithSameClientId); - new InMemoryRegisteredClientRepository(registrations); - }).withMessageStartingWith("Registered client must be unique. Found duplicate client identifier:"); - } - - @Test - public void constructorListRegisteredClientWhenDuplicateClientSecretThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> { - RegisteredClient anotherRegistrationWithSameClientSecret = TestRegisteredClients.registeredClient2() - .clientSecret(this.registration.getClientSecret()) - .build(); - List registrations = Arrays.asList(this.registration, - anotherRegistrationWithSameClientSecret); - new InMemoryRegisteredClientRepository(registrations); - }).withMessageStartingWith("Registered client must be unique. Found duplicate client secret for identifier:"); - } - - @Test - public void findByIdWhenFoundThenFound() { - String id = this.registration.getId(); - assertThat(this.clients.findById(id)).isEqualTo(this.registration); - } - - @Test - public void findByIdWhenNotFoundThenNull() { - String missingId = this.registration.getId() + "MISSING"; - assertThat(this.clients.findById(missingId)).isNull(); - } - - @Test - public void findByIdWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.clients.findById(null)) - .withMessageContaining("id cannot be empty"); - } - - @Test - public void findByClientIdWhenFoundThenFound() { - String clientId = this.registration.getClientId(); - assertThat(this.clients.findByClientId(clientId)).isEqualTo(this.registration); - } - - @Test - public void findByClientIdWhenNotFoundThenNull() { - String missingClientId = this.registration.getClientId() + "MISSING"; - assertThat(this.clients.findByClientId(missingClientId)).isNull(); - } - - @Test - public void findByClientIdWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.clients.findByClientId(null)) - .withMessageContaining("clientId cannot be empty"); - } - - @Test - public void saveWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.clients.save(null)) - .withMessageContaining("registeredClient cannot be null"); - } - - @Test - public void saveWhenExistingIdThenUpdate() { - RegisteredClient registeredClient = createRegisteredClient(this.registration.getId(), "client-id-2", - "client-secret-2"); - this.clients.save(registeredClient); - RegisteredClient savedClient = this.clients.findByClientId(registeredClient.getClientId()); - assertThat(savedClient).isEqualTo(registeredClient); - } - - @Test - public void saveWhenExistingClientIdThenThrowIllegalArgumentException() { - RegisteredClient registeredClient = createRegisteredClient("client-2", this.registration.getClientId(), - "client-secret-2"); - assertThatIllegalArgumentException().isThrownBy(() -> this.clients.save(registeredClient)) - .withMessage("Registered client must be unique. Found duplicate client identifier: " - + registeredClient.getClientId()); - } - - @Test - public void saveWhenExistingClientSecretThenThrowIllegalArgumentException() { - RegisteredClient registeredClient = createRegisteredClient("client-2", "client-id-2", - this.registration.getClientSecret()); - assertThatIllegalArgumentException().isThrownBy(() -> this.clients.save(registeredClient)) - .withMessage("Registered client must be unique. Found duplicate client secret for identifier: " - + registeredClient.getId()); - } - - @Test - public void saveWhenSavedAndFindByIdThenFound() { - RegisteredClient registeredClient = createRegisteredClient(); - this.clients.save(registeredClient); - RegisteredClient savedClient = this.clients.findById(registeredClient.getId()); - assertThat(savedClient).isEqualTo(registeredClient); - } - - @Test - public void saveWhenSavedAndFindByClientIdThenFound() { - RegisteredClient registeredClient = createRegisteredClient(); - this.clients.save(registeredClient); - RegisteredClient savedClient = this.clients.findByClientId(registeredClient.getClientId()); - assertThat(savedClient).isEqualTo(registeredClient); - } - - private static RegisteredClient createRegisteredClient() { - return createRegisteredClient("client-2", "client-id-2", "client-secret-2"); - } - - private static RegisteredClient createRegisteredClient(String id, String clientId, String clientSecret) { - // @formatter:off - return RegisteredClient.withId(id) - .clientId(clientId) - .clientSecret(clientSecret) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://client.example.com") - .scope("scope1") - .build(); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepositoryTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepositoryTests.java deleted file mode 100644 index 451c0fcd2..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepositoryTests.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.client; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.PreparedStatementSetter; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.jackson2.SecurityJackson2Modules; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientRowMapper; -import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.util.StringUtils; - -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.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link JdbcRegisteredClientRepository}. - * - * @author Rafal Lewczuk - * @author Steve Riesenberg - * @author Joe Grandja - * @author Ovidiu Popa - */ -public class JdbcRegisteredClientRepositoryTests { - - private static final String OAUTH2_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql"; - - private static final String OAUTH2_CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE = "/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql"; - - private EmbeddedDatabase db; - - private JdbcOperations jdbcOperations; - - private JdbcRegisteredClientRepository registeredClientRepository; - - @BeforeEach - public void setUp() { - this.db = createDb(OAUTH2_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE); - this.jdbcOperations = new JdbcTemplate(this.db); - this.registeredClientRepository = new JdbcRegisteredClientRepository(this.jdbcOperations); - } - - @AfterEach - public void tearDown() { - this.db.shutdown(); - } - - @Test - public void constructorWhenJdbcOperationsIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new JdbcRegisteredClientRepository(null)) - .withMessage("jdbcOperations cannot be null"); - // @formatter:on - } - - @Test - public void setRegisteredClientRowMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.registeredClientRepository.setRegisteredClientRowMapper(null)) - .withMessage("registeredClientRowMapper cannot be null"); - // @formatter:on - } - - @Test - public void setRegisteredClientParametersMapperWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.registeredClientRepository.setRegisteredClientParametersMapper(null)) - .withMessage("registeredClientParametersMapper cannot be null"); - // @formatter:on - } - - @Test - public void saveWhenRegisteredClientNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.registeredClientRepository.save(null)) - .withMessageContaining("registeredClient cannot be null"); - } - - @Test - public void saveWhenRegisteredClientExistsThenUpdated() { - RegisteredClient originalRegisteredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(originalRegisteredClient); - - RegisteredClient registeredClient = this.registeredClientRepository.findById(originalRegisteredClient.getId()); - assertThat(registeredClient).isEqualTo(originalRegisteredClient); - - RegisteredClient updatedRegisteredClient = RegisteredClient.from(originalRegisteredClient) - .clientId("test") - .clientIdIssuedAt(Instant.now()) - .clientName("clientName") - .scope("scope2") - .build(); - - RegisteredClient expectedUpdatedRegisteredClient = RegisteredClient.from(originalRegisteredClient) - .clientName("clientName") - .scope("scope2") - .build(); - this.registeredClientRepository.save(updatedRegisteredClient); - - registeredClient = this.registeredClientRepository.findById(updatedRegisteredClient.getId()); - assertThat(registeredClient).isEqualTo(expectedUpdatedRegisteredClient); - assertThat(registeredClient).isNotEqualTo(originalRegisteredClient); - } - - @Test - public void saveWhenNewThenSaved() { - RegisteredClient expectedRegisteredClient = TestRegisteredClients.registeredClient() - .clientSettings( - ClientSettings.builder().tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256).build()) - .build(); - this.registeredClientRepository.save(expectedRegisteredClient); - RegisteredClient registeredClient = this.registeredClientRepository.findById(expectedRegisteredClient.getId()); - assertThat(registeredClient).isEqualTo(expectedRegisteredClient); - } - - @Test - public void saveWhenClientSecretNullThenSaved() { - RegisteredClient expectedRegisteredClient = TestRegisteredClients.registeredClient().clientSecret(null).build(); - this.registeredClientRepository.save(expectedRegisteredClient); - RegisteredClient registeredClient = this.registeredClientRepository.findById(expectedRegisteredClient.getId()); - assertThat(registeredClient).isEqualTo(expectedRegisteredClient); - } - - // gh-1641 - @Test - public void saveWhenMultipleWithClientSecretEmptyThenSaved() { - RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient() - .id("registration-1") - .clientId("client-1") - .clientSecret("") - .build(); - this.registeredClientRepository.save(registeredClient1); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient() - .id("registration-2") - .clientId("client-2") - .clientSecret("") - .build(); - this.registeredClientRepository.save(registeredClient2); - } - - @Test - public void saveWhenExistingClientIdThenThrowIllegalArgumentException() { - RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient() - .id("registration-1") - .clientId("client-1") - .build(); - this.registeredClientRepository.save(registeredClient1); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient() - .id("registration-2") - .clientId("client-1") - .build(); - assertThatIllegalArgumentException().isThrownBy(() -> this.registeredClientRepository.save(registeredClient2)) - .withMessage("Registered client must be unique. Found duplicate client identifier: " - + registeredClient2.getClientId()); - } - - @Test - public void saveWhenExistingClientSecretThenThrowIllegalArgumentException() { - RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient() - .id("registration-1") - .clientId("client-1") - .clientSecret("secret") - .build(); - this.registeredClientRepository.save(registeredClient1); - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient() - .id("registration-2") - .clientId("client-2") - .clientSecret("secret") - .build(); - assertThatIllegalArgumentException().isThrownBy(() -> this.registeredClientRepository.save(registeredClient2)) - .withMessage("Registered client must be unique. Found duplicate client secret for identifier: " - + registeredClient2.getId()); - } - - @Test - public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception { - RowMapper registeredClientRowMapper = spy(new RegisteredClientRowMapper()); - this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper); - RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper(); - Function> registeredClientParametersMapper = spy( - clientParametersMapper); - this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - RegisteredClient result = this.registeredClientRepository.findById(registeredClient.getId()); - assertThat(result).isEqualTo(registeredClient); - verify(registeredClientRowMapper).mapRow(any(), anyInt()); - verify(registeredClientParametersMapper).apply(any()); - } - - @Test - public void findByIdWhenIdNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.registeredClientRepository.findById(null)) - .withMessage("id cannot be empty"); - // @formatter:on - } - - @Test - public void findByIdWhenExistsThenFound() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - RegisteredClient result = this.registeredClientRepository.findById(registeredClient.getId()); - assertThat(result).isEqualTo(registeredClient); - } - - @Test - public void findByIdWhenNotExistsThenNotFound() { - RegisteredClient result = this.registeredClientRepository.findById("not-exists"); - assertThat(result).isNull(); - } - - @Test - public void findByClientIdWhenClientIdNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.registeredClientRepository.findByClientId(null)) - .withMessage("clientId cannot be empty"); - // @formatter:on - } - - @Test - public void findByClientIdWhenExistsThenFound() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - RegisteredClient result = this.registeredClientRepository.findByClientId(registeredClient.getClientId()); - assertThat(result).isEqualTo(registeredClient); - } - - @Test - public void findByClientIdWhenNotExistsThenNotFound() { - RegisteredClient result = this.registeredClientRepository.findByClientId("not-exists"); - assertThat(result).isNull(); - } - - @Test - public void tableDefinitionWhenCustomThenAbleToOverride() { - EmbeddedDatabase db = createDb(OAUTH2_CUSTOM_REGISTERED_CLIENT_SCHEMA_SQL_RESOURCE); - CustomJdbcRegisteredClientRepository registeredClientRepository = new CustomJdbcRegisteredClientRepository( - new JdbcTemplate(db)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - registeredClientRepository.save(registeredClient); - RegisteredClient foundRegisteredClient1 = registeredClientRepository.findById(registeredClient.getId()); - assertThat(foundRegisteredClient1).isEqualTo(registeredClient); - RegisteredClient foundRegisteredClient2 = registeredClientRepository - .findByClientId(registeredClient.getClientId()); - assertThat(foundRegisteredClient2).isEqualTo(registeredClient); - db.shutdown(); - } - - private static EmbeddedDatabase createDb(String schema) { - // @formatter:off - return new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript(schema) - .build(); - // @formatter:on - } - - private static final class CustomJdbcRegisteredClientRepository extends JdbcRegisteredClientRepository { - - // @formatter:off - private static final String COLUMN_NAMES = "id, " - + "clientId, " - + "clientIdIssuedAt, " - + "clientSecret, " - + "clientSecretExpiresAt, " - + "clientName, " - + "clientAuthenticationMethods, " - + "authorizationGrantTypes, " - + "redirectUris, " - + "postLogoutRedirectUris, " - + "scopes, " - + "clientSettings," - + "tokenSettings"; - // @formatter:on - - private static final String TABLE_NAME = "oauth2RegisteredClient"; - - private static final String LOAD_REGISTERED_CLIENT_SQL = "SELECT " + COLUMN_NAMES + " FROM " + TABLE_NAME - + " WHERE "; - - // @formatter:off - private static final String INSERT_REGISTERED_CLIENT_SQL = "INSERT INTO " + TABLE_NAME - + " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - // @formatter:on - - private CustomJdbcRegisteredClientRepository(JdbcOperations jdbcOperations) { - super(jdbcOperations); - setRegisteredClientRowMapper(new CustomRegisteredClientRowMapper()); - } - - @Override - public void save(RegisteredClient registeredClient) { - List parameters = getRegisteredClientParametersMapper().apply(registeredClient); - PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); - getJdbcOperations().update(INSERT_REGISTERED_CLIENT_SQL, pss); - } - - @Override - public RegisteredClient findById(String id) { - return findBy("id = ?", id); - } - - @Override - public RegisteredClient findByClientId(String clientId) { - return findBy("clientId = ?", clientId); - } - - private RegisteredClient findBy(String filter, Object... args) { - List result = getJdbcOperations().query(LOAD_REGISTERED_CLIENT_SQL + filter, - getRegisteredClientRowMapper(), args); - return !result.isEmpty() ? result.get(0) : null; - } - - private static final class CustomRegisteredClientRowMapper implements RowMapper { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - private CustomRegisteredClientRowMapper() { - ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader(); - List securityModules = SecurityJackson2Modules.getModules(classLoader); - this.objectMapper.registerModules(securityModules); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Override - public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException { - Timestamp clientIdIssuedAt = rs.getTimestamp("clientIdIssuedAt"); - Timestamp clientSecretExpiresAt = rs.getTimestamp("clientSecretExpiresAt"); - Set clientAuthenticationMethods = StringUtils - .commaDelimitedListToSet(rs.getString("clientAuthenticationMethods")); - Set authorizationGrantTypes = StringUtils - .commaDelimitedListToSet(rs.getString("authorizationGrantTypes")); - Set redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirectUris")); - Set postLogoutRedirectUris = StringUtils - .commaDelimitedListToSet(rs.getString("postLogoutRedirectUris")); - Set clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes")); - - // @formatter:off - RegisteredClient.Builder builder = RegisteredClient.withId(rs.getString("id")) - .clientId(rs.getString("clientId")) - .clientIdIssuedAt((clientIdIssuedAt != null) ? clientIdIssuedAt.toInstant() : null) - .clientSecret(rs.getString("clientSecret")) - .clientSecretExpiresAt((clientSecretExpiresAt != null) ? clientSecretExpiresAt.toInstant() : null) - .clientName(rs.getString("clientName")) - .clientAuthenticationMethods((authenticationMethods) -> - clientAuthenticationMethods.forEach((authenticationMethod) -> - authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod)))) - .authorizationGrantTypes((grantTypes) -> - authorizationGrantTypes.forEach((grantType) -> - grantTypes.add(resolveAuthorizationGrantType(grantType)))) - .redirectUris((uris) -> uris.addAll(redirectUris)) - .postLogoutRedirectUris((uris) -> uris.addAll(postLogoutRedirectUris)) - .scopes((scopes) -> scopes.addAll(clientScopes)); - // @formatter:on - - Map clientSettingsMap = parseMap(rs.getString("clientSettings")); - builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build()); - - Map tokenSettingsMap = parseMap(rs.getString("tokenSettings")); - builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build()); - - return builder.build(); - } - - private Map parseMap(String data) { - try { - return this.objectMapper.readValue(data, new TypeReference<>() { - }); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex.getMessage(), ex); - } - } - - private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) { - if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.AUTHORIZATION_CODE; - } - else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.CLIENT_CREDENTIALS; - } - else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) { - return AuthorizationGrantType.REFRESH_TOKEN; - } - // Custom authorization grant type - return new AuthorizationGrantType(authorizationGrantType); - } - - private static ClientAuthenticationMethod resolveClientAuthenticationMethod( - String clientAuthenticationMethod) { - if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.CLIENT_SECRET_BASIC; - } - else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.CLIENT_SECRET_POST; - } - else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) { - return ClientAuthenticationMethod.NONE; - } - // Custom client authentication method - return new ClientAuthenticationMethod(clientAuthenticationMethod); - } - - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java deleted file mode 100644 index c7c759424..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientTests.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.client; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link RegisteredClient}. - * - * @author Anoop Garlapati - */ -public class RegisteredClientTests { - - private static final String ID = "registration-1"; - - private static final String CLIENT_ID = "client-1"; - - private static final String CLIENT_SECRET = "secret"; - - private static final Set REDIRECT_URIS = Collections.singleton("https://example.com"); - - private static final Set POST_LOGOUT_REDIRECT_URIS = Collections - .singleton("https://example.com/oidc-post-logout"); - - private static final Set SCOPES = Collections - .unmodifiableSet(Stream.of("openid", "profile", "email").collect(Collectors.toSet())); - - private static final Set CLIENT_AUTHENTICATION_METHODS = Collections - .singleton(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - - @Test - public void buildWhenAuthorizationGrantTypesNotSetThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenAllAttributesProvidedThenAllAttributesAreSet() { - Instant clientIdIssuedAt = Instant.now(); - Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS); - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientIdIssuedAt(clientIdIssuedAt) - .clientSecret(CLIENT_SECRET) - .clientSecretExpiresAt(clientSecretExpiresAt) - .clientName("client-name") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .postLogoutRedirectUris( - (postLogoutRedirectUris) -> postLogoutRedirectUris.addAll(POST_LOGOUT_REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getId()).isEqualTo(ID); - assertThat(registration.getClientId()).isEqualTo(CLIENT_ID); - assertThat(registration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt); - assertThat(registration.getClientSecret()).isEqualTo(CLIENT_SECRET); - assertThat(registration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt); - assertThat(registration.getClientName()).isEqualTo("client-name"); - assertThat(registration.getAuthorizationGrantTypes()) - .isEqualTo(Collections.singleton(AuthorizationGrantType.AUTHORIZATION_CODE)); - assertThat(registration.getClientAuthenticationMethods()).isEqualTo(CLIENT_AUTHENTICATION_METHODS); - assertThat(registration.getRedirectUris()).isEqualTo(REDIRECT_URIS); - assertThat(registration.getPostLogoutRedirectUris()).isEqualTo(POST_LOGOUT_REDIRECT_URIS); - assertThat(registration.getScopes()).isEqualTo(SCOPES); - } - - @Test - public void buildWhenIdIsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(null)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenClientIdIsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(null) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenRedirectUrisNotProvidedThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenRedirectUrisConsumerClearsSetThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://example.com") - .redirectUris(Set::clear) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenClientAuthenticationMethodNotProvidedThenDefaultToBasic() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getClientAuthenticationMethods()) - .isEqualTo(Collections.singleton(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)); - } - - @Test - public void buildWhenScopeIsEmptyThenScopeNotRequired() { - RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .build(); - } - - @Test - public void buildWhenScopeConsumerIsProvidedThenConsumerAccepted() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getScopes()).isEqualTo(SCOPES); - } - - @Test - public void buildWhenScopeContainsSpaceThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scope("openid profile") - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenScopeContainsInvalidCharacterThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scope("an\"invalid\"scope") - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenRedirectUriInvalidThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("invalid URI") - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenRedirectUriContainsFragmentThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://example.com/page#fragment") - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenPostLogoutRedirectUriInvalidThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .postLogoutRedirectUri("invalid URI") - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenPostLogoutRedirectUriContainsFragmentThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://example.com") - .postLogoutRedirectUri("https://example.com/index#fragment") - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenTwoAuthorizationGrantTypesAreProvidedThenBothAreRegistered() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getAuthorizationGrantTypes()).containsExactlyInAnyOrder( - AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS); - } - - @Test - public void buildWhenAuthorizationGrantTypesConsumerIsProvidedThenConsumerAccepted() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantTypes((authorizationGrantTypes) -> { - authorizationGrantTypes.add(AuthorizationGrantType.AUTHORIZATION_CODE); - authorizationGrantTypes.add(AuthorizationGrantType.CLIENT_CREDENTIALS); - }) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getAuthorizationGrantTypes()).containsExactlyInAnyOrder( - AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS); - } - - @Test - public void buildWhenAuthorizationGrantTypesConsumerClearsSetThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantTypes(Set::clear) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenTwoClientAuthenticationMethodsAreProvidedThenBothAreRegistered() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getClientAuthenticationMethods()).containsExactlyInAnyOrder( - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, ClientAuthenticationMethod.CLIENT_SECRET_POST); - } - - @Test - public void buildWhenClientAuthenticationMethodsConsumerIsProvidedThenConsumerAccepted() { - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethods((clientAuthenticationMethods) -> { - clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST); - }) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getClientAuthenticationMethods()).containsExactlyInAnyOrder( - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, ClientAuthenticationMethod.CLIENT_SECRET_POST); - } - - @Test - public void buildWhenOverrideIdThenOverridden() { - String overriddenId = "override"; - RegisteredClient registration = RegisteredClient.withId(ID) - .id(overriddenId) - .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getId()).isEqualTo(overriddenId); - } - - @Test - public void buildWhenRegisteredClientProvidedThenMakesACopy() { - RegisteredClient registration = TestRegisteredClients.registeredClient().build(); - RegisteredClient updated = RegisteredClient.from(registration).build(); - - assertThat(registration.getId()).isEqualTo(updated.getId()); - assertThat(registration.getClientId()).isEqualTo(updated.getClientId()); - assertThat(registration.getClientIdIssuedAt()).isEqualTo(updated.getClientIdIssuedAt()); - assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret()); - assertThat(registration.getClientSecretExpiresAt()).isEqualTo(updated.getClientSecretExpiresAt()); - assertThat(registration.getClientName()).isEqualTo(updated.getClientName()); - assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods()); - assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods()); - assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes()); - assertThat(registration.getAuthorizationGrantTypes()).isNotSameAs(updated.getAuthorizationGrantTypes()); - assertThat(registration.getRedirectUris()).isEqualTo(updated.getRedirectUris()); - assertThat(registration.getRedirectUris()).isNotSameAs(updated.getRedirectUris()); - assertThat(registration.getPostLogoutRedirectUris()).isEqualTo(updated.getPostLogoutRedirectUris()); - assertThat(registration.getPostLogoutRedirectUris()).isNotSameAs(updated.getPostLogoutRedirectUris()); - assertThat(registration.getScopes()).isEqualTo(updated.getScopes()); - assertThat(registration.getScopes()).isNotSameAs(updated.getScopes()); - assertThat(registration.getClientSettings()).isEqualTo(updated.getClientSettings()); - assertThat(registration.getClientSettings()).isNotSameAs(updated.getClientSettings()); - assertThat(registration.getTokenSettings()).isEqualTo(updated.getTokenSettings()); - assertThat(registration.getTokenSettings()).isNotSameAs(updated.getTokenSettings()); - } - - @Test - public void buildWhenRegisteredClientValuesOverriddenThenPropagated() { - RegisteredClient registration = TestRegisteredClients.registeredClient().build(); - String newName = "client-name"; - String newSecret = "new-secret"; - String newScope = "new-scope"; - String newRedirectUri = "https://another-redirect-uri.com"; - String newPostLogoutRedirectUri = "https://another-post-logout-redirect-uri.com"; - RegisteredClient updated = RegisteredClient.from(registration) - .clientName(newName) - .clientSecret(newSecret) - .scopes((scopes) -> { - scopes.clear(); - scopes.add(newScope); - }) - .redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add(newRedirectUri); - }) - .postLogoutRedirectUris((postLogoutRedirectUris) -> { - postLogoutRedirectUris.clear(); - postLogoutRedirectUris.add(newPostLogoutRedirectUri); - }) - .build(); - - assertThat(registration.getClientName()).isNotEqualTo(newName); - assertThat(updated.getClientName()).isEqualTo(newName); - assertThat(registration.getClientSecret()).isNotEqualTo(newSecret); - assertThat(updated.getClientSecret()).isEqualTo(newSecret); - assertThat(registration.getScopes()).doesNotContain(newScope); - assertThat(updated.getScopes()).containsExactly(newScope); - assertThat(registration.getRedirectUris()).doesNotContain(newRedirectUri); - assertThat(updated.getRedirectUris()).containsExactly(newRedirectUri); - assertThat(registration.getPostLogoutRedirectUris()).doesNotContain(newPostLogoutRedirectUri); - assertThat(updated.getPostLogoutRedirectUris()).containsExactly(newPostLogoutRedirectUri); - } - - @Test - public void buildWhenPublicClientTypeThenDefaultSettings() { - Instant clientIdIssuedAt = Instant.now(); - RegisteredClient registration = RegisteredClient.withId(ID) - .clientId(CLIENT_ID) - .clientIdIssuedAt(clientIdIssuedAt) - .clientName("client-name") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) - .redirectUris((redirectUris) -> redirectUris.addAll(REDIRECT_URIS)) - .scopes((scopes) -> scopes.addAll(SCOPES)) - .build(); - - assertThat(registration.getId()).isEqualTo(ID); - assertThat(registration.getClientId()).isEqualTo(CLIENT_ID); - assertThat(registration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt); - assertThat(registration.getClientName()).isEqualTo("client-name"); - assertThat(registration.getAuthorizationGrantTypes()) - .isEqualTo(Collections.singleton(AuthorizationGrantType.AUTHORIZATION_CODE)); - assertThat(registration.getClientAuthenticationMethods()) - .isEqualTo(Collections.singleton(ClientAuthenticationMethod.NONE)); - assertThat(registration.getRedirectUris()).isEqualTo(REDIRECT_URIS); - assertThat(registration.getScopes()).isEqualTo(SCOPES); - assertThat(registration.getClientSettings().isRequireProofKey()).isTrue(); - assertThat(registration.getClientSettings().isRequireAuthorizationConsent()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java deleted file mode 100644 index e08851d32..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.client; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; - -/** - * @author Anoop Garlapati - */ -public final class TestRegisteredClients { - - private TestRegisteredClients() { - } - - public static RegisteredClient.Builder registeredClient() { - return RegisteredClient.withId("registration-1") - .clientId("client-1") - .clientIdIssuedAt(Instant.now().truncatedTo(ChronoUnit.SECONDS)) - .clientSecret("secret-1") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://example.com/callback-1") - .redirectUri("https://example.com/callback-2") - .redirectUri("https://example.com/callback-3") - .postLogoutRedirectUri("https://example.com/oidc-post-logout") - .scope("scope1"); - } - - public static RegisteredClient.Builder registeredClient2() { - return RegisteredClient.withId("registration-2") - .clientId("client-2") - .clientIdIssuedAt(Instant.now().truncatedTo(ChronoUnit.SECONDS)) - .clientSecret("secret-2") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .redirectUri("https://example.com") - .postLogoutRedirectUri("https://example.com/oidc-post-logout") - .scope("scope1") - .scope("scope2"); - } - - public static RegisteredClient.Builder registeredPublicClient() { - return RegisteredClient.withId("registration-3") - .clientId("client-3") - .clientIdIssuedAt(Instant.now().truncatedTo(ChronoUnit.SECONDS)) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) - .redirectUri("https://example.com") - .scope("scope1") - .clientSettings(ClientSettings.builder().requireProofKey(true).build()); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfigurationTests.java deleted file mode 100644 index cb632b3ee..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/OAuth2AuthorizationServerConfigurationTests.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.config.annotation.web.configuration; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.Ordered; -import org.springframework.core.annotation.OrderUtils; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2AuthorizationServerConfiguration}. - * - * @author Joe Grandja - */ -public class OAuth2AuthorizationServerConfigurationTests { - - @Test - public void assertOrderHighestPrecedence() { - Method authorizationServerSecurityFilterChainMethod = ClassUtils.getMethod( - OAuth2AuthorizationServerConfiguration.class, "authorizationServerSecurityFilterChain", - HttpSecurity.class); - Integer order = OrderUtils.getOrder(authorizationServerSecurityFilterChainMethod); - assertThat(order).isEqualTo(Ordered.HIGHEST_PRECEDENCE); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessorTests.java deleted file mode 100644 index e637698f0..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configuration/RegisterMissingBeanPostProcessorTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.config.annotation.web.configuration; - -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.RootBeanDefinition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.endsWith; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link RegisterMissingBeanPostProcessor}. - * - * @author Steve Riesenberg - */ -public class RegisterMissingBeanPostProcessorTests { - - private final RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor(); - - @Test - public void postProcessBeanDefinitionRegistryWhenClassAddedThenRegisteredWithClass() { - this.postProcessor.addBeanDefinition(SimpleBean.class, null); - this.postProcessor.setBeanFactory(new DefaultListableBeanFactory()); - - BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - this.postProcessor.postProcessBeanDefinitionRegistry(beanDefinitionRegistry); - - ArgumentCaptor beanDefinitionCaptor = ArgumentCaptor.forClass(BeanDefinition.class); - verify(beanDefinitionRegistry).registerBeanDefinition(endsWith("SimpleBean"), beanDefinitionCaptor.capture()); - - RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionCaptor.getValue(); - assertThat(beanDefinition.getBeanClass()).isEqualTo(SimpleBean.class); - assertThat(beanDefinition.getInstanceSupplier()).isNull(); - } - - @Test - public void postProcessBeanDefinitionRegistryWhenSupplierAddedThenRegisteredWithSupplier() { - Supplier beanSupplier = () -> new SimpleBean("string"); - this.postProcessor.addBeanDefinition(SimpleBean.class, beanSupplier); - this.postProcessor.setBeanFactory(new DefaultListableBeanFactory()); - - BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - this.postProcessor.postProcessBeanDefinitionRegistry(beanDefinitionRegistry); - - ArgumentCaptor beanDefinitionCaptor = ArgumentCaptor.forClass(BeanDefinition.class); - verify(beanDefinitionRegistry).registerBeanDefinition(endsWith("SimpleBean"), beanDefinitionCaptor.capture()); - - RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionCaptor.getValue(); - assertThat(beanDefinition.getBeanClass()).isEqualTo(SimpleBean.class); - assertThat(beanDefinition.getInstanceSupplier()).isEqualTo(beanSupplier); - } - - @Test - public void postProcessBeanDefinitionRegistryWhenNoBeanDefinitionsAddedThenNoneRegistered() { - this.postProcessor.setBeanFactory(new DefaultListableBeanFactory()); - - BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - this.postProcessor.postProcessBeanDefinitionRegistry(beanDefinitionRegistry); - verifyNoInteractions(beanDefinitionRegistry); - } - - @Test - public void postProcessBeanDefinitionRegistryWhenBeanDefinitionAlreadyExistsThenNoneRegistered() { - this.postProcessor.addBeanDefinition(SimpleBean.class, null); - DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition("simpleBean", new RootBeanDefinition(SimpleBean.class)); - this.postProcessor.setBeanFactory(beanFactory); - - BeanDefinitionRegistry beanDefinitionRegistry = mock(BeanDefinitionRegistry.class); - this.postProcessor.postProcessBeanDefinitionRegistry(beanDefinitionRegistry); - verifyNoInteractions(beanDefinitionRegistry); - } - - private static final class SimpleBean { - - private final String field; - - private SimpleBean(String field) { - this.field = field; - } - - private String getField() { - return this.field; - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilterTests.java deleted file mode 100644 index 23532a7cd..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/AuthorizationServerContextFilterTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link AuthorizationServerContextFilter}. - * - * @author Joe Grandja - */ -class AuthorizationServerContextFilterTests { - - private static final String SCHEME = "https"; - - private static final String HOST = "example.com"; - - private static final int PORT = 8443; - - private static final String DEFAULT_ISSUER = SCHEME + "://" + HOST + ":" + PORT; - - private AuthorizationServerContextFilter filter; - - @Test - void doFilterWhenDefaultEndpointsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/issuer1"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - @Test - void doFilterWhenCustomEndpointsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .authorizationEndpoint("/oauth2/v1/authorize") - .deviceAuthorizationEndpoint("/oauth2/v1/device_authorization") - .deviceVerificationEndpoint("/oauth2/v1/device_verification") - .tokenEndpoint("/oauth2/v1/token") - .jwkSetEndpoint("/oauth2/v1/jwks") - .tokenRevocationEndpoint("/oauth2/v1/revoke") - .tokenIntrospectionEndpoint("/oauth2/v1/introspect") - .oidcClientRegistrationEndpoint("/connect/v1/register") - .oidcUserInfoEndpoint("/v1/userinfo") - .oidcLogoutEndpoint("/connect/v1/logout") - .build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/issuer2"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - @Test - void doFilterWhenIssuerHasMultiplePathsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/path1/path2/issuer3"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - private void assertResolvedIssuer(String requestUri, String expectedIssuer) throws Exception { - MockHttpServletRequest request = createRequest(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - - AtomicReference resolvedIssuer = new AtomicReference<>(); - FilterChain filterChain = (req, resp) -> resolvedIssuer - .set(AuthorizationServerContextHolder.getContext().getIssuer()); - - this.filter.doFilter(request, response, filterChain); - - assertThat(resolvedIssuer.get()).isEqualTo(expectedIssuer); - } - - private static Set getEndpointUris(AuthorizationServerSettings authorizationServerSettings) { - Set endpointUris = new HashSet<>(); - endpointUris.add("/.well-known/oauth-authorization-server"); - endpointUris.add("/.well-known/openid-configuration"); - for (Map.Entry setting : authorizationServerSettings.getSettings().entrySet()) { - if (setting.getKey().endsWith("-endpoint")) { - endpointUris.add((String) setting.getValue()); - } - } - return endpointUris; - } - - private static MockHttpServletRequest createRequest(String requestUri) { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI(requestUri); - request.setScheme(SCHEME); - request.setServerName(HOST); - request.setServerPort(PORT); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizersTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizersTests.java deleted file mode 100644 index c7853cd56..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DefaultOAuth2TokenCustomizersTests.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -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.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link DefaultOAuth2TokenCustomizers}. - * - * @author Steve Riesenberg - * @author Joe Grandja - */ -class DefaultOAuth2TokenCustomizersTests { - - private static final String ISSUER_1 = "issuer-1"; - - private static final String ISSUER_2 = "issuer-2"; - - private JwsHeader.Builder jwsHeaderBuilder; - - private JwtClaimsSet.Builder jwtClaimsBuilder; - - @BeforeEach - void setUp() { - this.jwsHeaderBuilder = JwsHeader.with(SignatureAlgorithm.RS256); - this.jwtClaimsBuilder = JwtClaimsSet.builder().issuer(ISSUER_1); - } - - @Test - void customizeWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() { - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.REFRESH_TOKEN) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenAuthorizationGrantIsNullThenNoClaimsAdded() { - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getResources()).willReturn(Set.of("resource1", "resource2")); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - // We do not populate claims (e.g. `aud`) based on the resource parameter - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getAudiences()).willReturn(Set.of("audience1", "audience2")); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - // NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndDelegationThenActClaimAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getAudiences()).willReturn(Collections.emptySet()); - - Authentication subject = new TestingAuthenticationToken("subject", null); - OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor( - Map.of(JwtClaimNames.ISS, ISSUER_1, JwtClaimNames.SUB, "actor1")); - OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor( - Map.of(JwtClaimNames.ISS, ISSUER_2, JwtClaimNames.SUB, "actor2")); - OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken( - subject, List.of(actor1, actor2)); - - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .principal(principal) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - assertThat(jwtClaimsSet.getClaims().get("act")).isNotNull(); - @SuppressWarnings("unchecked") - Map actClaim1 = (Map) jwtClaimsSet.getClaims().get("act"); - assertThat(actClaim1.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_1); - assertThat(actClaim1.get(JwtClaimNames.SUB)).isEqualTo("actor1"); - @SuppressWarnings("unchecked") - Map actClaim2 = (Map) actClaim1.get("act"); - assertThat(actClaim2.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_2); - assertThat(actClaim2.get(JwtClaimNames.SUB)).isEqualTo("actor2"); - } - - @Test - void customizeWhenPKIX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .tokenSettings( - TokenSettings.builder() - .x509CertificateBoundAccessTokens(true) - .build() - ) - .build(); - // @formatter:on - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.TLS_CLIENT_AUTH, TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .registeredClient(registeredClient) - .authorizationGrant(clientCredentialsAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - Map cnfClaim = jwtClaimsSet.getClaim("cnf"); - assertThat(cnfClaim).isNotEmpty(); - assertThat(cnfClaim.get("x5t#S256")).isNotNull(); - } - - @Test - void customizeWhenSelfSignedX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl("https://client.example.com/jwks") - .build() - ) - .tokenSettings( - TokenSettings.builder() - .x509CertificateBoundAccessTokens(true) - .build() - ) - .build(); - // @formatter:on - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .registeredClient(registeredClient) - .authorizationGrant(clientCredentialsAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - Map cnfClaim = jwtClaimsSet.getClaim("cnf"); - assertThat(cnfClaim).isNotEmpty(); - assertThat(cnfClaim.get("x5t#S256")).isNotNull(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java deleted file mode 100644 index b1ae5b760..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the JWK Set endpoint. - * - * @author Florian Berthe - */ -@ExtendWith(SpringTestContextExtension.class) -public class JwkSetTests { - - private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenJwkSetThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - assertJwkSetRequestThenReturnKeys(DEFAULT_JWK_SET_ENDPOINT_URI); - } - - @Test - public void requestWhenJwkSetCustomEndpointThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - assertJwkSetRequestThenReturnKeys(this.authorizationServerSettings.getJwkSetEndpoint()); - } - - @Test - public void requestWhenJwkSetRequestIncludesIssuerPathThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - String issuer = "https://example.com:8443/issuer1"; - assertJwkSetRequestThenReturnKeys(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())); - } - - private void assertJwkSetRequestThenReturnKeys(String jwkSetEndpointUri) throws Exception { - this.mvc.perform(get(jwkSetEndpointUri)) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.keys").isNotEmpty()) - .andExpect(jsonPath("$.keys").isArray()); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder() - .jwkSetEndpoint("/test/jwks") - .multipleIssuersAllowed(true) - .build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java deleted file mode 100644 index 55a2bdbe7..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java +++ /dev/null @@ -1,1509 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.text.MessageFormat; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - -import com.jayway.jsonpath.JsonPath; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.assertj.core.matcher.AssertionMatcher; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Authorization Code Grant. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @author Dmitriy Dubson - * @author Steve Riesenberg - * @author Greg Li - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2AuthorizationCodeGrantTests { - - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static final String STATE_URL_UNENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ+004pwm9j55li7BoydXYysH4enZMF21Q"; - - private static final String STATE_URL_ENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ%2B004pwm9j55li7BoydXYysH4enZMF21Q"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtEncoder jwtEncoder; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static AuthorizationServerSettings authorizationServerSettings; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static AuthenticationConverter authorizationRequestConverter; - - private static Consumer> authorizationRequestConvertersConsumer; - - private static AuthenticationProvider authorizationRequestAuthenticationProvider; - - private static Consumer> authorizationRequestAuthenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authorizationResponseHandler; - - private static AuthenticationFailureHandler authorizationErrorResponseHandler; - - private static SecurityContextRepository securityContextRepository; - - private static String consentPage = "/oauth2/consent"; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired(required = false) - private OAuth2TokenGenerator tokenGenerator; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtEncoder = new NimbusJwtEncoder(jwkSource); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - authorizationServerSettings = AuthorizationServerSettings.builder() - .authorizationEndpoint("/test/authorize") - .tokenEndpoint("/test/token") - .build(); - authorizationRequestConverter = mock(AuthenticationConverter.class); - authorizationRequestConvertersConsumer = mock(Consumer.class); - authorizationRequestAuthenticationProvider = mock(AuthenticationProvider.class); - authorizationRequestAuthenticationProvidersConsumer = mock(Consumer.class); - authorizationResponseHandler = mock(AuthenticationSuccessHandler.class); - authorizationErrorResponseHandler = mock(AuthenticationFailureHandler.class); - securityContextRepository = spy(new HttpSessionSecurityContextRepository()); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @BeforeEach - public void setup() { - reset(securityContextRepository); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient))) - .andExpect(status().isUnauthorized()) - .andReturn(); - } - - @Test - public void requestWhenRegisteredClientMissingThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - this.mvc - .perform( - get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))) - .andExpect(status().isBadRequest()) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationRequestAuthenticatedThenRedirectToClient() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - assertAuthorizationRequestRedirectsToClient(DEFAULT_AUTHORIZATION_ENDPOINT_URI); - } - - @Test - public void requestWhenAuthorizationRequestCustomEndpointThenRedirectToClient() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - assertAuthorizationRequestRedirectsToClient(authorizationServerSettings.getAuthorizationEndpoint()); - } - - private void assertAuthorizationRequestRedirectsToClient(String authorizationEndpointUri) throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://example.com/callback-1?param=encoded%20parameter%20value"); // gh-1011 - }).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(authorizationEndpointUri).queryParams(authorizationRequestParameters).with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String redirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - String code = extractParameterFromRedirectUri(redirectedUrl, "code"); - assertThat(redirectedUrl).isEqualTo(redirectUri + "&code=" + code + "&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - } - - @Test - public void requestWhenTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessTokenResponse accessTokenResponse = assertTokenRequestReturnsAccessTokenResponse(registeredClient, - authorization, DEFAULT_TOKEN_ENDPOINT_URI); - - // Assert user authorities was propagated as claim in JWT - Jwt jwt = this.jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); - List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - } - - @Test - public void requestWhenTokenRequestCustomEndpointThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - assertTokenRequestReturnsAccessTokenResponse(registeredClient, authorization, - authorizationServerSettings.getTokenEndpoint()); - } - - private OAuth2AccessTokenResponse assertTokenRequestReturnsAccessTokenResponse(RegisteredClient registeredClient, - OAuth2Authorization authorization, String tokenEndpointUri) throws Exception { - MvcResult mvcResult = this.mvc - .perform(post(tokenEndpointUri).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - assertThat(accessTokenAuthorization.getRefreshToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - @Test - public void requestWhenPublicClientWithPkceThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService - .findById(authorizationCodeAuthorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - // gh-1430 - @Test - public void requestWhenPublicClientWithPkceAndCustomRefreshTokenGeneratorThenReturnRefreshToken() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - - OAuth2Authorization authorization = this.authorizationService.findById(authorizationCodeAuthorization.getId()); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getRefreshToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = authorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - // gh-1680 - @Test - public void requestWhenPublicClientWithPkceAndEmptyCodeThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap tokenRequestParameters = new LinkedMultiValueMap<>(); - tokenRequestParameters.set(OAuth2ParameterNames.GRANT_TYPE, - AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - tokenRequestParameters.set(OAuth2ParameterNames.CODE, ""); - tokenRequestParameters.set(OAuth2ParameterNames.REDIRECT_URI, - registeredClient.getRedirectUris().iterator().next()); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(tokenRequestParameters) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)) - .andExpect(status().isBadRequest()); - } - - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isBadRequest()); - } - - // gh-1011 - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeThenErrorResponseEncoded() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - String redirectUri = "https://example.com/callback-1?param=encoded%20parameter%20value"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add(redirectUri); - }).clientSettings(ClientSettings.builder().requireProofKey(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = redirectUri + "&" + "error=invalid_request&" + "error_description=" - + UriUtils.encode("OAuth 2.0 Parameter: code_challenge", StandardCharsets.UTF_8) + "&" + "error_uri=" - + UriUtils.encode("https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1", StandardCharsets.UTF_8) - + "&" + "state=" + STATE_URL_ENCODED; - assertThat(redirectedUrl).isEqualTo(expectedRedirectUri); - } - - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isBadRequest()); - } - - @Test - public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()); - - verify(this.tokenGenerator, times(2)).generate(any()); - } - - @Test - public void requestWhenRequiresConsentThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - String consentPage = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is2xxSuccessful()) - .andReturn() - .getResponse() - .getContentAsString(); - - assertThat(consentPage).contains("Consent required"); - assertThat(consentPage).contains(scopeCheckbox("message.read")); - assertThat(consentPage).contains(scopeCheckbox("message.write")); - } - - @Test - public void requestWhenConsentRequestThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName("user") - .build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) - .state(STATE_URL_UNENCODED) - .build(); - authorization = OAuth2Authorization.from(authorization) - .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) - .build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.SCOPE, "message.read") - .param(OAuth2ParameterNames.SCOPE, "message.write") - .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl) - .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - } - - @Test - public void requestWhenCustomConsentPageConfiguredThenRedirect() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomConsentPage.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); - - String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - MultiValueMap redirectQueryParams = uriComponents.getQueryParams(); - - assertThat(uriComponents.getPath()).isEqualTo(consentPage); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) - .isEqualTo(registeredClient.getClientId()); - - String state = extractParameterFromRedirectUri(redirectedUrl, "state"); - OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(authorization).isNotNull(); - } - - @Test - public void requestWhenCustomConsentCustomizerConfiguredThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomConsentRequest.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder() - .requireAuthorizationConsent(true) - .setting("custom.allowed-authorities", "authority-1 authority-2") - .build()) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) - .state(STATE_URL_UNENCODED) - .build(); - authorization = OAuth2Authorization.from(authorization) - .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) - .build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param("authority", "authority-1 authority-2") - .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) - .with(user("principal"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl) - .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.access_token").value(new AssertionMatcher() { - @Override - public void assertion(String accessToken) throws AssertionError { - Jwt jwt = OAuth2AuthorizationCodeGrantTests.this.jwtDecoder.decode(accessToken); - assertThat(jwt.getClaimAsStringList(AUTHORITIES_CLAIM)).containsExactlyInAnyOrder("authority-1", - "authority-2"); - } - })) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").doesNotExist()) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plus(5, ChronoUnit.MINUTES)); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, - registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, - registeredClient.getScopes()); - given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult); - given(authorizationRequestAuthenticationProvider - .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); - given(authorizationRequestAuthenticationProvider.authenticate(any())) - .willReturn(authorizationCodeRequestAuthenticationResult); - - this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().isOk()); - - verify(authorizationRequestConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authorizationRequestConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authorizationRequestConverter - || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter - || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); - - verify(authorizationRequestAuthenticationProvider) - .authenticate(eq(authorizationCodeRequestAuthenticationResult)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authorizationRequestAuthenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders) - .allMatch((provider) -> provider == authorizationRequestAuthenticationProvider - || provider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider - || provider instanceof OAuth2AuthorizationConsentAuthenticationProvider); - - verify(authorizationResponseHandler).onAuthenticationSuccess(any(), any(), - eq(authorizationCodeRequestAuthenticationResult)); - } - - // gh-482 - @Test - public void requestWhenClientObtainsAccessTokenThenClientAuthenticationNotPersisted() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - ArgumentCaptor securityContextCaptor = ArgumentCaptor - .forClass(org.springframework.security.core.context.SecurityContext.class); - verify(securityContextRepository, times(1)).saveContext(securityContextCaptor.capture(), any(), any()); - assertThat(securityContextCaptor.getValue().getAuthentication()) - .isInstanceOf(UsernamePasswordAuthenticationToken.class); - reset(securityContextRepository); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository - .loadDeferredContext(mvcResult.getRequest()) - .get(); - assertThat(securityContext.getAuthentication()).isNull(); - } - - @Test - public void requestWhenAuthorizationAndTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - MvcResult mvcResult = this.mvc - .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); - } - - @Test - public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - @Test - public void requestWhenPushedAuthorizationRequestThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) - .param(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) - .param(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.request_uri").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andReturn(); - - String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); - - mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService - .findById(authorizationCodeAuthorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - parameters.set(OAuth2ParameterNames.STATE, STATE_URL_UNENCODED); - return parameters; - } - - private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, - OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - return parameters; - } - - private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { - String clientId = registeredClient.getClientId(); - String clientSecret = registeredClient.getClientSecret(); - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + clientSecret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); - } - - private static String scopeCheckbox(String scope) { - return MessageFormat.format( - "", scope); - } - - private String extractParameterFromRedirectUri(String redirectUri, String param) - throws UnsupportedEncodingException { - String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - return uriComponents.getQueryParams().getFirst(param); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) - && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator - extends AuthorizationServerConfiguration { - - @Bean - JwtEncoder jwtEncoder() { - return jwtEncoder; - } - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); - return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); - } - - private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator refreshTokenGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - @Nullable - @Override - public OAuth2RefreshToken generate(OAuth2TokenContext context) { - if (!OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { - return null; - } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); - return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithSecurityContextRepository - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, Customizer.withDefaults()) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ) - .securityContext((securityContext) -> - securityContext.securityContextRepository(securityContextRepository)); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { - - @Bean - JwtEncoder jwtEncoder() { - return jwtEncoder; - } - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( - jwtGenerator, refreshTokenGenerator); - return spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return authorizationServerSettings; - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomConsentPage extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint.consentPage(consentPage)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomConsentRequest extends AuthorizationServerConfiguration { - - @Autowired - private OAuth2AuthorizationConsentService authorizationConsentService; - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint.authenticationProviders(configureAuthenticationProviders())) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - @Override - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) - && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(context.getRegisteredClient().getId(), context.getPrincipal().getName()); - - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : authorizationConsent.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - private Consumer> configureAuthenticationProviders() { - return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { - if (authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider) { - ((OAuth2AuthorizationConsentAuthenticationProvider) authenticationProvider) - .setAuthorizationConsentCustomizer(new AuthorizationConsentCustomizer()); - } - }); - } - - static class AuthorizationConsentCustomizer - implements Consumer { - - @Override - public void accept( - OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext) { - OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = authorizationConsentAuthenticationContext - .getAuthorizationConsent(); - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication = authorizationConsentAuthenticationContext - .getAuthentication(); - Map additionalParameters = authorizationConsentAuthentication.getAdditionalParameters(); - RegisteredClient registeredClient = authorizationConsentAuthenticationContext.getRegisteredClient(); - ClientSettings clientSettings = registeredClient.getClientSettings(); - - Set requestedAuthorities = authorities((String) additionalParameters.get("authority")); - Set allowedAuthorities = authorities(clientSettings.getSetting("custom.allowed-authorities")); - for (String requestedAuthority : requestedAuthorities) { - if (allowedAuthorities.contains(requestedAuthority)) { - authorizationConsentBuilder.authority(new SimpleGrantedAuthority(requestedAuthority)); - } - } - } - - private static Set authorities(String param) { - Set authorities = new HashSet<>(); - if (param != null) { - List authorityValues = Arrays.asList(param.split(" ")); - authorities.addAll(authorityValues); - } - - return authorities; - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomAuthorizationEndpoint extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint - .authorizationRequestConverter(authorizationRequestConverter) - .authorizationRequestConverters(authorizationRequestConvertersConsumer) - .authenticationProvider(authorizationRequestAuthenticationProvider) - .authenticationProviders(authorizationRequestAuthenticationProvidersConsumer) - .authorizationResponseHandler(authorizationResponseHandler) - .errorResponseHandler(authorizationErrorResponseHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed - extends AuthorizationServerConfigurationWithTokenGenerator { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPushedAuthorizationRequests - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java deleted file mode 100644 index abe46c787..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.hasItems; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Authorization Server Metadata endpoint. - * - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2AuthorizationServerMetadataTests { - - private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; - - private static final String ISSUER = "https://example.com"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @BeforeAll - public static void setupClass() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestAndIssuerSetThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(ISSUER)) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - String host = "https://example.com:8443"; - - String issuerPath = "/issuer1"; - String issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - - issuerPath = "/path1/issuer2"; - issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - - issuerPath = "/path1/path2/issuer3"; - issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - } - - // gh-616 - @Test - public void requestWhenAuthorizationServerMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - hasItems("scope1", "scope2"))); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - registeredClientRepository.save(registeredClient); - return registeredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithMetadataCustomizer extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .authorizationServerMetadataEndpoint((authorizationServerMetadataEndpoint) -> - authorizationServerMetadataEndpoint - .authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer())) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer authorizationServerMetadataCustomizer() { - return (authorizationServerMetadata) -> authorizationServerMetadata.scope("scope1").scope("scope2"); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java deleted file mode 100644 index 13c4373a0..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java +++ /dev/null @@ -1,688 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.X509ClientCertificateAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenExchangeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Client Credentials Grant. - * - * @author Alexey Nesterov - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ClientCredentialsGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static OAuth2TokenCustomizer jwtCustomizer; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtCustomizer = mock(OAuth2TokenCustomizer.class); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - reset(jwtCustomizer); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc - .perform(MockMvcRequestBuilders.post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void requestWhenTokenRequestValidThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenRequestPostsClientCredentialsThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.CLIENT_SECRET, registeredClient.getClientSecret())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenRequestPostsClientCredentialsAndRequiresUpgradingThenClientSecretUpgraded() - throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomPasswordEncoder.class).autowire(); - - String clientSecret = "secret-2"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .clientSecret("{noop}" + clientSecret) - .build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.CLIENT_SECRET, clientSecret)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - RegisteredClient updatedRegisteredClient = this.registeredClientRepository - .findByClientId(registeredClient.getClientId()); - assertThat(updatedRegisteredClient.getClientSecret()).startsWith("{bcrypt}"); - } - - @Test - public void requestWhenTokenRequestWithPKIX509ClientCertificateThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).with(x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - // gh-1635 - @Test - public void requestWhenTokenRequestIncludesBasicClientCredentialsAndX509ClientCertificateThenTokenResponse() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).with(x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - given(authenticationConverter.convert(any())).willReturn(clientCredentialsAuthentication); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - given(authenticationProvider.supports(eq(OAuth2ClientCredentialsAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(accessTokenAuthentication); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2AuthorizationCodeAuthenticationConverter - || converter instanceof OAuth2RefreshTokenAuthenticationConverter - || converter instanceof OAuth2ClientCredentialsAuthenticationConverter - || converter instanceof OAuth2DeviceCodeAuthenticationConverter - || converter instanceof OAuth2TokenExchangeAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2AuthorizationCodeAuthenticationProvider - || provider instanceof OAuth2RefreshTokenAuthenticationProvider - || provider instanceof OAuth2ClientCredentialsAuthenticationProvider - || provider instanceof OAuth2DeviceCodeAuthenticationProvider - || provider instanceof OAuth2TokenExchangeAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication)); - } - - @Test - public void requestWhenClientAuthenticationCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - new ClientAuthenticationMethod("custom"), null); - given(authenticationConverter.convert(any())).willReturn(clientPrincipal); - given(authenticationProvider.supports(eq(OAuth2ClientAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(clientPrincipal); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).param(OAuth2ParameterNames.GRANT_TYPE, - AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof JwtClientAssertionAuthenticationConverter - || converter instanceof ClientSecretBasicAuthenticationConverter - || converter instanceof ClientSecretPostAuthenticationConverter - || converter instanceof PublicClientAuthenticationConverter - || converter instanceof X509ClientCertificateAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(clientPrincipal)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof JwtClientAssertionAuthenticationProvider - || provider instanceof X509ClientCertificateAuthenticationProvider - || provider instanceof ClientSecretAuthenticationProvider - || provider instanceof PublicClientAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal)); - } - - @Test - public void requestWhenTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); - } - - @Test - public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return jwtCustomizer; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenEndpoint extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .tokenEndpoint((tokenEndpoint) -> - tokenEndpoint - .accessTokenRequestConverter(authenticationConverter) - .accessTokenRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .accessTokenResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomPasswordEncoder extends AuthorizationServerConfiguration { - - @Override - PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomClientAuthentication extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - authenticationSuccessHandler = spy(authenticationSuccessHandler()); - - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .clientAuthentication((clientAuthentication) -> - clientAuthentication - .authenticationConverter(authenticationConverter) - .authenticationConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .authenticationSuccessHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private AuthenticationSuccessHandler authenticationSuccessHandler() { - return new AuthenticationSuccessHandler() { - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder - .createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); - } - }; - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java deleted file mode 100644 index 338438f07..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java +++ /dev/null @@ -1,695 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.security.Principal; -import java.time.Instant; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Device Grant. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2DeviceCodeGrantTests { - - private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization"; - - private static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); - - private static final String USER_CODE = "ABCD-EFGH"; - - private static final String STATE = "123"; - - private static final String DEVICE_CODE = "abc-XYZ"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static final HttpMessageConverter deviceAuthorizationResponseHttpMessageConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); - - private static final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private OAuth2AuthorizationConsentService authorizationConsentService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - // @formatter:off - db = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - // @formatter:on - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenRegisteredClientMissingThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizationResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.device_code").isNotEmpty()) - .andExpect(jsonPath("$.user_code").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.verification_uri").isNotEmpty()) - .andExpect(jsonPath("$.verification_uri_complete").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = deviceAuthorizationResponseHttpMessageConverter - .read(OAuth2DeviceAuthorizationResponse.class, httpResponse); - String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue(); - assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}"); - assertThat(deviceAuthorizationResponse.getVerificationUri()) - .isEqualTo("https://example.com:8443/oauth2/device_verification"); - assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) - .isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode); - - String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE); - assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull(); - assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull(); - } - - @Test - public void requestWhenDeviceVerificationRequestUnauthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - - // @formatter:off - this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .queryParams(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)) - .queryParams(parameters) - .with(user("user"))) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) - .andReturn(); - // @formatter:on - - String responseHtml = mvcResult.getResponse().getContentAsString(); - assertThat(responseHtml).contains("Consent required"); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization.getPrincipalName()).isEqualTo("user"); - assertThat(updatedAuthorization).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(false); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName("user") - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .attribute(OAuth2ParameterNames.STATE, STATE) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); - parameters.set(OAuth2ParameterNames.STATE, STATE); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isBadRequest()); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSuccessPage() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName("user") - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .attribute(OAuth2ParameterNames.STATE, STATE) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); - parameters.set(OAuth2ParameterNames.STATE, STATE); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .params(parameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - - assertThat(mvcResult.getResponse().getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") - .scope(registeredClient.getScopes().iterator().next()) - .build(); - // @formatter:on - this.authorizationConsentService.save(authorizationConsent); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization).isNotNull(); - assertThat(updatedAuthorization.getAccessToken()).isNotNull(); - assertThat(updatedAuthorization.getRefreshToken()).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization accessTokenAuthorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(accessTokenAuthorization).isEqualTo(updatedAuthorization); - } - - @Test - public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") - .scope(registeredClient.getScopes().iterator().next()) - .build(); - // @formatter:on - this.authorizationConsentService.save(authorizationConsent); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - // @formatter:on - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); - return headers; - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java deleted file mode 100644 index 186e5cc67..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2RefreshTokenGrantTests.java +++ /dev/null @@ -1,645 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.Principal; -import java.security.PublicKey; -import java.time.Instant; -import java.util.Base64; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.Transient; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Refresh Token Grant. - * - * @author Alexey Nesterov - * @since 0.0.3 - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2RefreshTokenGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtDecoder jwtDecoder; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build(); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenRefreshTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - // Assert user authorities was propagated as claim in JWT - Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); - List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - } - - // gh-432 - @Test - public void requestWhenRevokeAndRefreshThenAccessTokenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isActive()).isTrue(); - } - - // gh-1430 - @Test - public void requestWhenRefreshTokenRequestWithPublicClientThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofThenReturnDPoPBoundAccessToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, - "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); - Map accessTokenClaims = new HashMap<>(); - Map cnfClaim = new HashMap<>(); - cnfClaim.put("jkt", TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - accessTokenClaims.put("cnf", cnfClaim); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, accessToken, accessTokenClaims) - .build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndAccessTokenNotBoundThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) - .andExpect(jsonPath("$.error_description").value("jkt claim is missing.")); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndDifferentPublicKeyThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, - "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); - Map accessTokenClaims = new HashMap<>(); - // Bind access token to different public key - PublicKey publicKey = TestJwks.DEFAULT_RSA_JWK.toPublicKey(); - Map cnfClaim = new HashMap<>(); - cnfClaim.put("jkt", computeSHA256(publicKey)); - accessTokenClaims.put("cnf", cnfClaim); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, accessToken, accessTokenClaims) - .build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) - .andExpect(jsonPath("$.error_description").value("jwk header is invalid.")); - } - - @Test - public void requestWhenRefreshTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - private static MultiValueMap getRefreshTokenRequestParameters(OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); - parameters.set(OAuth2ParameterNames.REFRESH_TOKEN, authorization.getRefreshToken().getToken().getTokenValue()); - return parameters; - } - - private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPublicClientAuthentication - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain( - HttpSecurity http, RegisteredClientRepository registeredClientRepository) throws Exception { - - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .clientAuthentication((clientAuthentication) -> - clientAuthentication - .authenticationConverter( - new PublicClientRefreshTokenAuthenticationConverter()) - .authenticationProvider( - new PublicClientRefreshTokenAuthenticationProvider(registeredClientRepository))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @Transient - private static final class PublicClientRefreshTokenAuthenticationToken extends OAuth2ClientAuthenticationToken { - - private PublicClientRefreshTokenAuthenticationToken(String clientId) { - super(clientId, ClientAuthenticationMethod.NONE, null, null); - } - - private PublicClientRefreshTokenAuthenticationToken(RegisteredClient registeredClient) { - super(registeredClient, ClientAuthenticationMethod.NONE, null); - } - - } - - private static final class PublicClientRefreshTokenAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - // grant_type (REQUIRED) - String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) { - return null; - } - - // client_id (REQUIRED) - String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId)) { - return null; - } - - return new PublicClientRefreshTokenAuthenticationToken(clientId); - } - - } - - private static final class PublicClientRefreshTokenAuthenticationProvider implements AuthenticationProvider { - - private final RegisteredClientRepository registeredClientRepository; - - private PublicClientRefreshTokenAuthenticationProvider(RegisteredClientRepository registeredClientRepository) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - this.registeredClientRepository = registeredClientRepository; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - PublicClientRefreshTokenAuthenticationToken publicClientAuthentication = (PublicClientRefreshTokenAuthenticationToken) authentication; - - if (!ClientAuthenticationMethod.NONE.equals(publicClientAuthentication.getClientAuthenticationMethod())) { - return null; - } - - String clientId = publicClientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (!registeredClient.getClientAuthenticationMethods() - .contains(publicClientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); - } - - return new PublicClientRefreshTokenAuthenticationToken(registeredClient); - } - - @Override - public boolean supports(Class authentication) { - return PublicClientRefreshTokenAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static void throwInvalidClient(String parameterName) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Public client authentication failed: " + parameterName, null); - throw new OAuth2AuthenticationException(error); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java deleted file mode 100644 index e3418802c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.security.Principal; -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Token Exchange Grant. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenExchangeGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String RESOURCE = "https://mydomain.com/resource"; - - private static final String AUDIENCE = "audience"; - - private static final String SUBJECT_TOKEN = "EfYu_0jEL"; - - private static final String ACTOR_TOKEN = "JlNE_xR1f"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - public final SpringTestContext spring = new SpringTestContext(); - - private final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - AuthorizationServerConfiguration.JWK_SOURCE = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - // @formatter:off - AuthorizationServerConfiguration.DB = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - // @formatter:on - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - AuthorizationServerConfiguration.DB.shutdown(); - } - - @Test - public void requestWhenAccessTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - this.authorizationService.save(subjectAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, - subjectAuthorization.getAccessToken().getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE); - parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims()).isNotNull(); - // We do not populate claims (e.g. `aud`) based on the resource or audience - // parameters - assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD)) - .isEqualTo(List.of(registeredClient.getClientId())); - assertThat(authorization.getRefreshToken()).isNull(); - assertThat(authorization.getAttribute(Principal.class.getName())).isEqualTo(userPrincipal); - } - - @Test - public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); - Map actorTokenClaims = new HashMap<>(); - actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); - actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); - Map subjectTokenClaims = new HashMap<>(); - subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); - subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); - subjectTokenClaims.put("may_act", actorTokenClaims); - OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); - OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), adminPrincipal) - .build(); - // @formatter:on - this.authorizationService.save(subjectAuthorization); - this.authorizationService.save(actorAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull(); - assertThat(authorization.getRefreshToken()).isNull(); - assertThat(authorization.getAttribute(Principal.class.getName())) - .isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class); - } - - @Test - public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); - Map actorTokenClaims = new HashMap<>(); - actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); - actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); - Map subjectTokenClaims = new HashMap<>(); - subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); - subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); - subjectTokenClaims.put("may_act", actorTokenClaims); - OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); - OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), adminPrincipal) - .build(); - // @formatter:on - this.authorizationService.save(subjectAuthorization); - this.authorizationService.save(actorAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - // @formatter:on - } - - private static OAuth2AccessToken createAccessToken(String tokenValue) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); - } - - private static UsernamePasswordAuthenticationToken createUserPrincipal(String username) { - User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER")); - return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); - } - - private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); - return headers; - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - static JWKSource JWK_SOURCE; - - static EmbeddedDatabase DB; - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(DB); - } - - @Bean - JWKSource jwkSource() { - return JWK_SOURCE; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java deleted file mode 100644 index 6a4d145d0..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java +++ /dev/null @@ -1,609 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.function.Consumer; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Token Introspection endpoint. - * - * @author Gerardo Roza - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenIntrospectionTests { - - private static EmbeddedDatabase db; - - private static OAuth2TokenCustomizer accessTokenCustomizer; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private static final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); - - private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @BeforeAll - public static void init() { - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - reset(accessTokenCustomizer); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenIntrospectValidAccessTokenThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() - .clientSecret("secret-2") - .build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - issuedAt, expiresAt, new HashSet<>(Arrays.asList("scope1", "scope2"))); - // @formatter:off - OAuth2TokenClaimsSet accessTokenClaims = OAuth2TokenClaimsSet.builder() - .issuer("https://provider.com") - .subject("subject") - .audience(Collections.singletonList(authorizedRegisteredClient.getClientId())) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes()) - .id("id") - .build(); - // @formatter:on - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(authorizedRegisteredClient, accessToken, accessTokenClaims.getClaims()) - .build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), - accessTokenClaims.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( - accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes()); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); - assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( - accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); - } - - @Test - public void requestWhenIntrospectValidRefreshTokenThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() - .clientSecret("secret-2") - .build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - this.registeredClientRepository.save(authorizedRegisteredClient); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(refreshToken.getIssuedAt().minusSeconds(1), - refreshToken.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(refreshToken.getExpiresAt().minusSeconds(1), - refreshToken.getExpiresAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getScopes()).isNull(); - assertThat(tokenIntrospectionResponse.getTokenType()).isNull(); - assertThat(tokenIntrospectionResponse.getNotBefore()).isNull(); - assertThat(tokenIntrospectionResponse.getSubject()).isNull(); - assertThat(tokenIntrospectionResponse.getAudience()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuer()).isNull(); - assertThat(tokenIntrospectionResponse.getId()).isNull(); - } - - @Test - public void requestWhenObtainReferenceAccessTokenAndIntrospectThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build(); - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .build(); - // @formatter:on - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenEndpoint()) - .params(getAuthorizationCodeTokenRequestParameters(authorizedRegisteredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(authorizedRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(mvcResult); - OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - // @formatter:off - mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - - ArgumentCaptor accessTokenClaimsContextCaptor = ArgumentCaptor - .forClass(OAuth2TokenClaimsContext.class); - verify(accessTokenCustomizer).customize(accessTokenClaimsContextCaptor.capture()); - - OAuth2TokenClaimsContext accessTokenClaimsContext = accessTokenClaimsContextCaptor.getValue(); - OAuth2TokenClaimsSet accessTokenClaims = accessTokenClaimsContext.getClaims().build(); - - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), - accessTokenClaims.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( - accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); - List scopes = new ArrayList<>(accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE)); - assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(scopes); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); - assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( - accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); - } - - @Test - public void requestWhenTokenIntrospectionEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - - given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - // @formatter:off - this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()); - // @formatter:on - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2TokenIntrospectionAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(tokenIntrospectionAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2TokenIntrospectionAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), - eq(tokenIntrospectionAuthentication)); - } - - @Test - public void requestWhenIntrospectionRequestIncludesIssuerPathThenActive() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - - given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - this.mvc.perform(post(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()); - // @formatter:on - } - - private static MultiValueMap getTokenIntrospectionRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception { - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse); - } - - private static MultiValueMap getAuthorizationCodeTokenRequestParameters( - RegisteredClient registeredClient, OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - return parameters; - } - - private static OAuth2AccessTokenResponse readAccessTokenResponse(MvcResult mvcResult) throws Exception { - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { - String clientId = registeredClient.getClientId(); - String clientSecret = registeredClient.getClientSecret(); - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + clientSecret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); - } - - @Bean - OAuth2TokenCustomizer accessTokenCustomizer() { - return accessTokenCustomizer; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .tokenIntrospectionEndpoint((tokenIntrospectionEndpoint) -> - tokenIntrospectionEndpoint - .introspectionRequestConverter(authenticationConverter) - .introspectionRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .introspectionResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Override - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder() - .multipleIssuersAllowed(true) - .tokenIntrospectionEndpoint("/test/introspect") - .build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java deleted file mode 100644 index 5c4b34a2d..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.List; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Token Revocation endpoint. - * - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenRevocationTests { - - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenRevokeRefreshTokenThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2RefreshToken token = authorization.getRefreshToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.REFRESH_TOKEN; - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - } - - @Test - public void requestWhenRevokeAccessTokenThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isFalse(); - } - - @Test - public void requestWhenRevokeAccessTokenAndRequestIncludesIssuerPathThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - // @formatter:on - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isFalse(); - } - - @Test - public void requestWhenTokenRevocationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenRevocationEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - token, clientPrincipal); - - given(authenticationConverter.convert(any())).willReturn(tokenRevocationAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenRevocationAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenRevocationAuthentication); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2TokenRevocationAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(tokenRevocationAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2TokenRevocationAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenRevocationAuthentication)); - } - - private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenRevocationEndpoint - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .tokenRevocationEndpoint((tokenRevocationEndpoint) -> - tokenRevocationEndpoint - .revocationRequestConverter(authenticationConverter) - .revocationRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .revocationResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java deleted file mode 100644 index 63239d026..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import javax.crypto.spec.SecretKeySpec; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletResponse; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.assertj.core.data.TemporalUnitWithinOffset; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; -import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.CollectionUtils; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OpenID Connect Dynamic Client Registration 1.0. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Dmitriy Dubson - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcClientRegistrationTests { - - private static final String ISSUER = "https://example.com:8443/issuer1"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register"; - - private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static JWKSet clientJwkSet; - - private static JwtEncoder jwtClientAssertionEncoder; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private MockWebServer server; - - private String clientJwkSetUrl; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - clientJwkSet = new JWKSet(TestJwks.generateRsaJwk().build()); - jwtClientAssertionEncoder = new NimbusJwtEncoder( - (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet)); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - } - - @BeforeEach - public void setup() throws Exception { - this.server = new MockWebServer(); - this.server.start(); - this.clientJwkSetUrl = this.server.url("/jwks").toString(); - // @formatter:off - MockResponse response = new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(clientJwkSet.toString()); - // @formatter:on - this.server.enqueue(response); - given(authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).willReturn(true); - } - - @AfterEach - public void tearDown() throws Exception { - this.server.shutdown(); - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - assertThat(clientRegistrationResponse.getClientId()).isNotNull(); - assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistration.getClientName()); - assertThat(clientRegistrationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getRedirectUris()); - assertThat(clientRegistrationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getGrantTypes()); - assertThat(clientRegistrationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientRegistrationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getScopes()); - assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientRegistrationResponse.getRegistrationClientUrl()).isNotNull(); - assertThat(clientRegistrationResponse.getRegistrationAccessToken()).isNotEmpty(); - } - - @Test - public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken()); - - MvcResult mvcResult = this.mvc - .perform(get(clientRegistrationResponse.getRegistrationClientUrl().toURI()).headers(httpHeaders)) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - - assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId()); - assertThat(clientConfigurationResponse.getClientIdIssuedAt()) - .isEqualTo(clientRegistrationResponse.getClientIdIssuedAt()); - assertThat(clientConfigurationResponse.getClientSecret()).isNotNull(); - assertThat(clientConfigurationResponse.getClientSecretExpiresAt()) - .isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt()); - assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName()); - assertThat(clientConfigurationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris()); - assertThat(clientConfigurationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes()); - assertThat(clientConfigurationResponse.getResponseTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getResponseTypes()); - assertThat(clientConfigurationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes()); - assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()); - assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientConfigurationResponse.getRegistrationClientUrl()) - .isEqualTo(clientRegistrationResponse.getRegistrationClientUrl()); - assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - willAnswer((invocation) -> { - HttpServletResponse response = invocation.getArgument(1, HttpServletResponse.class); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(HttpStatus.CREATED); - new OidcClientRegistrationHttpMessageConverter().write(clientRegistration, null, httpResponse); - return null; - }).given(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - - registerClient(clientRegistration); - - verify(authenticationConverter).convert(any()); - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).hasSize(2) - .allMatch((converter) -> converter == authenticationConverter - || converter instanceof OidcClientRegistrationAuthenticationConverter); - - verify(authenticationProvider).authenticate(any()); - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).hasSize(3) - .allMatch((provider) -> provider == authenticationProvider - || provider instanceof OidcClientRegistrationAuthenticationProvider - || provider instanceof OidcClientConfigurationAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verifyNoInteractions(authenticationFailureHandler); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() - throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - given(authenticationProvider.authenticate(any())).willThrow(new OAuth2AuthenticationException("error")); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)) - .param(OAuth2ParameterNames.CLIENT_ID, "invalid") - .with(jwt())); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - verifyNoInteractions(authenticationSuccessHandler); - } - - // gh-1056 - @Test - public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1") - .with(httpBasic(clientRegistrationResponse.getClientId(), - clientRegistrationResponse.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1")) - .andReturn(); - } - - // gh-1344 - @Test - public void requestWhenClientRegistersWithClientSecretJwtThenClientAuthenticationSuccess() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder() - .issuer(clientRegistrationResponse.getClientId()) - .subject(clientRegistrationResponse.getClientId()) - .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .build(); - - JWKSet jwkSet = new JWKSet( - TestJwks.jwk(new SecretKeySpec(clientRegistrationResponse.getClientSecret().getBytes(), "HS256")) - .build()); - JwtEncoder jwtClientAssertionEncoder = new NimbusJwtEncoder( - (jwkSelector, securityContext) -> jwkSelector.select(jwkSet)); - - Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1") - .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) - .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1")); - } - - @Test - public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredClient() throws Exception { - this.spring.register(CustomClientMetadataConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .claim("custom-metadata-name-1", "value-1") - .claim("custom-metadata-name-2", "value-2") - .claim("non-registered-custom-metadata", "value-3") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-1")).isEqualTo("value-1"); - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-2")).isEqualTo("value-2"); - assertThat(clientRegistrationResponse.getClaim("non-registered-custom-metadata")).isNull(); - - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-1")) - .isEqualTo("value-1"); - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-2")) - .isEqualTo("value-2"); - assertThat(registeredClient.getClientSettings().getSetting("non-registered-custom-metadata")).isNull(); - } - - // gh-2111 - @Test - public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception { - this.spring.register(ClientSecretExpirationConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24)); - TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES); - - // Returned response contains expiration date - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - // Persisted RegisteredClient contains expiration date - assertThat(registeredClient).isNotNull(); - assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - } - - private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { - // ***** (1) Obtain the "initial" access token used for registering the client - - String clientRegistrationScope = "client.create"; - // @formatter:off - RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-1") - .clientId("client-registrar-1") - .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .scope(clientRegistrationScope) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl(this.clientJwkSetUrl) - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256) - .build() - ) - .build(); - // @formatter:on - this.registeredClientRepository.save(clientRegistrar); - - // @formatter:off - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256) - .build(); - JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(clientRegistrar) - .build(); - // @formatter:on - Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - MvcResult mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) - .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) - .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrar.getClientId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - // ***** (2) Register the client - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // Register the client - mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) - .andExpect(status().isCreated()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - return readClientRegistrationResponse(mvcResult.getResponse()); - } - - private JwtClaimsSet.Builder jwtClientAssertionClaims(RegisteredClient registeredClient) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return JwtClaimsSet.builder() - .issuer(registeredClient.getClientId()) - .subject(registeredClient.getClientId()) - .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) - .issuedAt(issuedAt) - .expiresAt(expiresAt); - } - - private static String asUrl(String uri, String path) { - return UriComponentsBuilder.fromUriString(uri).path(path).build().toUriString(); - } - - private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static byte[] getClientRegistrationRequestContent(OidcClientRegistration clientRegistration) - throws Exception { - MockHttpOutputMessage httpRequest = new MockHttpOutputMessage(); - clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); - return httpRequest.getBodyAsBytes(); - } - - private static OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .clientRegistrationRequestConverter(authenticationConverter) - .clientRegistrationRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .clientRegistrationResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientMetadataConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - List supportedCustomClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2"); - if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(supportedCustomClientMetadata)); - provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(supportedCustomClientMetadata)); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter()); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint(Customizer.withDefaults()) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - registeredClientRepository.save(registeredClient); - return registeredClientRepository; - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - } - - private static final class CustomRegisteredClientConverter - implements Converter { - - private final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); - - private final List supportedCustomClientMetadata; - - private CustomRegisteredClientConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public RegisteredClient convert(OidcClientRegistration clientRegistration) { - RegisteredClient registeredClient = this.delegate.convert(clientRegistration); - - ClientSettings.Builder clientSettingsBuilder = ClientSettings - .withSettings(registeredClient.getClientSettings().getSettings()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - clientRegistration.getClaims().forEach((claim, value) -> { - if (this.supportedCustomClientMetadata.contains(claim)) { - clientSettingsBuilder.setting(claim, value); - } - }); - } - - return RegisteredClient.from(registeredClient).clientSettings(clientSettingsBuilder.build()).build(); - } - - } - - private static final class CustomClientRegistrationConverter - implements Converter { - - private final RegisteredClientOidcClientRegistrationConverter delegate = new RegisteredClientOidcClientRegistrationConverter(); - - private final List supportedCustomClientMetadata; - - private CustomClientRegistrationConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public OidcClientRegistration convert(RegisteredClient registeredClient) { - OidcClientRegistration clientRegistration = this.delegate.convert(registeredClient); - - Map clientMetadata = new HashMap<>(clientRegistration.getClaims()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - Map clientSettings = registeredClient.getClientSettings().getSettings(); - this.supportedCustomClientMetadata.forEach((customClaim) -> { - if (clientSettings.containsKey(customClaim)) { - clientMetadata.put(customClaim, clientSettings.get(customClaim)); - } - }); - } - - return OidcClientRegistration.withClaims(clientMetadata).build(); - } - - } - - /** - * This customization adds client secret expiration time by setting - * {@code RegisteredClient.clientSecretExpiresAt} during - * {@code OidcClientRegistration} -> {@code RegisteredClient} conversion - */ - private static final class ClientSecretExpirationRegisteredClientConverter - implements Converter { - - private static final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); - - @Override - public RegisteredClient convert(OidcClientRegistration clientRegistration) { - RegisteredClient registeredClient = delegate.convert(clientRegistration); - RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient); - - Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24)); - registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt); - - return registeredClientBuilder.build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java deleted file mode 100644 index e857577ad..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OpenID Connect 1.0 Provider Configuration endpoint. - * - * @author Sahariar Alam Khandoker - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcProviderConfigurationTests { - - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - - private static final String ISSUER = "https://example.com"; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @Autowired - private MockMvc mvc; - - @Test - public void requestWhenConfigurationRequestAndIssuerSetThenReturnDefaultConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)); - } - - @Test - public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - String issuer = "https://example.com:8443/issuer1"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - - issuer = "https://example.com:8443/path1/issuer2"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - - issuer = "https://example.com:8443/path1/path2/issuer3"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - } - - // gh-632 - @Test - public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)).with(user("user"))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)); - } - - // gh-616 - @Test - public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL))); - } - - @Test - public void requestWhenConfigurationRequestAndClientRegistrationEnabledThenConfigurationResponseIncludesRegistrationEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)) - .andExpect(jsonPath("$.registration_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint()))); - } - - private ResultMatcher[] defaultConfigurationMatchers(String issuer) { - // @formatter:off - return new ResultMatcher[] { - jsonPath("issuer").value(issuer), - jsonPath("authorization_endpoint").value(issuer.concat(this.authorizationServerSettings.getAuthorizationEndpoint())), - jsonPath("token_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenEndpoint())), - jsonPath("$.token_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("jwks_uri").value(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())), - jsonPath("userinfo_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())), - jsonPath("end_session_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())), - jsonPath("response_types_supported").value(OAuth2AuthorizationResponseType.CODE.getValue()), - jsonPath("$.grant_types_supported[0]").value(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - jsonPath("$.grant_types_supported[1]").value(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()), - jsonPath("$.grant_types_supported[2]").value(AuthorizationGrantType.REFRESH_TOKEN.getValue()), - jsonPath("revocation_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenRevocationEndpoint())), - jsonPath("$.revocation_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("introspection_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())), - jsonPath("$.introspection_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("$.code_challenge_methods_supported[0]").value("S256"), - jsonPath("subject_types_supported").value("public"), - jsonPath("id_token_signing_alg_values_supported").value(SignatureAlgorithm.RS256.getName()), - jsonPath("scopes_supported").value(OidcScopes.OPENID) - }; - // @formatter:on - } - - @Test - public void loadContextWhenIssuerNotValidUrlThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire()); - } - - @Test - public void loadContextWhenIssuerNotValidUriThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithQueryThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerQuery.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithFragmentThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerFragment.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithQueryAndFragmentThenThrowException() { - assertThatThrownBy(() -> this.spring.register(AuthorizationServerConfigurationWithIssuerQueryAndFragment.class) - .autowire()); - } - - @Test - public void loadContextWhenIssuerWithEmptyQueryThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyQuery.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithEmptyFragmentThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyFragment.class).autowire()); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer - .authorizationServer(); - // @formatter:off - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 - ); - // @formatter:on - return http.build(); - } - - @Bean - RegisteredClientRepository registeredClientRepository() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - return new InMemoryRegisteredClientRepository(registeredClient); - } - - @Bean - JWKSource jwkSource() { - return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithProviderConfigurationCustomizer - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc.providerConfigurationEndpoint((providerConfigurationEndpoint) -> - providerConfigurationEndpoint - .providerConfigurationCustomizer(providerConfigurationCustomizer()))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer providerConfigurationCustomizer() { - return (providerConfiguration) -> providerConfiguration.scope(OidcScopes.PROFILE).scope(OidcScopes.EMAIL); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithClientRegistrationEnabled - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc.clientRegistrationEndpoint(Customizer.withDefaults()) - ) - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("urn:example").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("https://not a valid uri").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "#fragment").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value#fragment").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "#").build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java deleted file mode 100644 index cd4de9ffe..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java +++ /dev/null @@ -1,787 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.util.Base64; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OpenID Connect 1.0. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcTests { - - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static SessionRegistry sessionRegistry; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired(required = false) - private OAuth2TokenGenerator tokenGenerator; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - sessionRegistry = spy(new SessionRegistryImpl()); - } - - @AfterEach - public void tearDown() { - if (this.jdbcOperations != null) { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user").roles("A", "B"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - Jwt idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - // Assert user authorities was propagated as claim in ID Token - List authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - - // Assert sid claim was added in ID Token - assertThat(idToken.getClaim("sid")).isNotNull(); - } - - // gh-1224 - @Test - public void requestWhenRefreshTokenRequestThenIdTokenContainsSidClaim() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user").roles("A", "B"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - Jwt idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - String sidClaim = idToken.getClaim("sid"); - assertThat(sidClaim).isNotNull(); - - // Refresh access token - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()) - .param(OAuth2ParameterNames.REFRESH_TOKEN, accessTokenResponse.getRefreshToken().getTokenValue()) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - servletResponse = mvcResult.getResponse(); - httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - - idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - assertThat(idToken.getClaim("sid")).isEqualTo(sidClaim); - } - - @Test - public void requestWhenLogoutRequestThenLogout() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - // Login - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(session.isNew()).isTrue(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - // Get ID Token - mvcResult = this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Logout - mvcResult = this.mvc - .perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)).param("id_token_hint", idToken) - .session(session)) - .andExpect(status().is3xxRedirection()) - .andReturn(); - redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - - assertThat(redirectedUrl).matches("/"); - assertThat(session.isInvalid()).isTrue(); - } - - @Test - public void requestWhenLogoutRequestWithOtherUsersIdTokenThenNotLogout() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // Login user1 - RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient1); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient1); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user1"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession user1Session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(user1Session.isNew()).isTrue(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization user1Authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient1, user1Authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient1.getClientId(), - registeredClient1.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String user1IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Login user2 - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient2); - - authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); - mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user2"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession user2Session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(user2Session.isNew()).isTrue(); - - redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization user2Authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient2, user2Authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient2.getClientId(), - registeredClient2.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - servletResponse = mvcResult.getResponse(); - httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - - String user2IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Attempt to log out user1 using user2's ID Token - mvcResult = this.mvc - .perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI).param("id_token_hint", user2IdToken).session(user1Session)) - .andExpect(status().isBadRequest()) - .andExpect(status().reason("[invalid_token] OpenID Connect 1.0 Logout Request Parameter: sub")) - .andReturn(); - - assertThat(user1Session.isInvalid()).isFalse(); - } - - @Test - public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(this.tokenGenerator, times(3)).generate(any()); - } - - // gh-1422 - @Test - public void requestWhenAuthenticationRequestWithOfflineAccessScopeThenTokenResponseIncludesRefreshToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(OidcScopes.OPENID) - .scope("offline_access") - .build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - } - - // gh-1422 - @Test - public void requestWhenAuthenticationRequestWithoutOfflineAccessScopeThenTokenResponseDoesNotIncludeRefreshToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - } - - private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - parameters.set(OAuth2ParameterNames.STATE, "state"); - return parameters; - } - - private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, - OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - private String extractParameterFromRedirectUri(String redirectUri, String param) - throws UnsupportedEncodingException { - String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - return uriComponents.getQueryParams().getFirst(param); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 - ); - // @formatter:on - return http.build(); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations, - registeredClientRepository); - authorizationService.setAuthorizationRowMapper(new RowMapper(registeredClientRepository)); - authorizationService.setAuthorizationParametersMapper(new ParametersMapper()); - return authorizationService; - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - @Bean - SessionRegistry sessionRegistry() { - return sessionRegistry; - } - - static class RowMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper { - - RowMapper(RegisteredClientRepository registeredClientRepository) { - super(registeredClientRepository); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - static class ParametersMapper extends JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper { - - ParametersMapper() { - super(); - getObjectMapper().addMixIn(TestingAuthenticationToken.class, TestingAuthenticationTokenMixin.class); - } - - } - - } - - @EnableWebSecurity - @Configuration - static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .tokenGenerator(tokenGenerator()) - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( - jwtGenerator, refreshTokenGenerator); - return spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - } - - } - - @EnableWebSecurity - @Configuration - static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .tokenGenerator(tokenGenerator()) - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); - return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); - } - - private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { - - private final OAuth2RefreshTokenGenerator delegate = new OAuth2RefreshTokenGenerator(); - - @Nullable - @Override - public OAuth2RefreshToken generate(OAuth2TokenContext context) { - if (context.getAuthorizedScopes().contains(OidcScopes.OPENID) - && !context.getAuthorizedScopes().contains("offline_access")) { - return null; - } - return this.delegate.generate(context); - } - - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java deleted file mode 100644 index d634ec261..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.config.annotation.web.configurers; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; -import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OpenID Connect 1.0 UserInfo endpoint. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcUserInfoTests { - - private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo"; - - private static SecurityContextRepository securityContextRepository; - - public final SpringTestContext spring = new SpringTestContext(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JwtEncoder jwtEncoder; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private static Function userInfoMapper; - - @BeforeAll - public static void init() { - securityContextRepository = spy(new HttpSessionSecurityContextRepository()); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - userInfoMapper = mock(Function.class); - } - - @BeforeEach - public void setup() { - reset(securityContextRepository); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - reset(userInfoMapper); - } - - @Test - public void requestWhenUserInfoRequestGetThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoRequestPostThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(post(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoRequestIncludesIssuerPathThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - String issuer = "https://example.com:8443/issuer1"; - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoEndpointCustomizedThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - given(userInfoMapper.apply(any())).willReturn(createUserInfo()); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - - verify(userInfoMapper).apply(any()); - verify(authenticationConverter).convert(any()); - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verifyNoInteractions(authenticationFailureHandler); - - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).hasSize(2) - .allMatch((provider) -> provider == authenticationProvider - || provider instanceof OidcUserInfoAuthenticationProvider); - - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).hasSize(2).allMatch(AuthenticationConverter.class::isInstance); - } - - @Test - public void requestWhenUserInfoEndpointCustomizedWithAuthenticationProviderThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - given(authenticationProvider.supports(eq(OidcUserInfoAuthenticationToken.class))).willReturn(true); - String tokenValue = authorization.getAccessToken().getToken().getTokenValue(); - Jwt jwt = this.jwtDecoder.decode(tokenValue); - OidcUserInfoAuthenticationToken oidcUserInfoAuthentication = new OidcUserInfoAuthenticationToken( - new JwtAuthenticationToken(jwt), createUserInfo()); - given(authenticationProvider.authenticate(any())).willReturn(oidcUserInfoAuthentication); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verify(authenticationProvider).authenticate(any()); - verifyNoInteractions(authenticationFailureHandler); - verifyNoInteractions(userInfoMapper); - } - - @Test - public void requestWhenUserInfoEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - given(userInfoMapper.apply(any())).willReturn(createUserInfo()); - willAnswer((invocation) -> { - HttpServletResponse response = invocation.getArgument(1); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("unauthorized"); - return null; - }).given(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - - OAuth2AccessToken accessToken = createAuthorization().getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is4xxClientError()); - // @formatter:on - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - verifyNoInteractions(authenticationSuccessHandler); - verifyNoInteractions(userInfoMapper); - } - - // gh-482 - @Test - public void requestWhenUserInfoRequestThenBearerTokenAuthenticationNotPersisted() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()) - .andReturn(); - // @formatter:on - - org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository - .loadDeferredContext(mvcResult.getRequest()) - .get(); - assertThat(securityContext.getAuthentication()).isNull(); - } - - private static ResultMatcher[] userInfoResponse() { - // @formatter:off - return new ResultMatcher[] { - jsonPath("sub").value("user1"), - jsonPath("name").value("First Last"), - jsonPath("given_name").value("First"), - jsonPath("family_name").value("Last"), - jsonPath("middle_name").value("Middle"), - jsonPath("nickname").value("User"), - jsonPath("preferred_username").value("user"), - jsonPath("profile").value("https://example.com/user1"), - jsonPath("picture").value("https://example.com/user1.jpg"), - jsonPath("website").value("https://example.com"), - jsonPath("email").value("user1@example.com"), - jsonPath("email_verified").value("true"), - jsonPath("gender").value("female"), - jsonPath("birthdate").value("1970-01-01"), - jsonPath("zoneinfo").value("Europe/Paris"), - jsonPath("locale").value("en-US"), - jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"), - jsonPath("phone_number_verified").value("false"), - jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"), - jsonPath("updated_at").value("1970-01-01T00:00:00Z") - }; - // @formatter:on - } - - private OAuth2Authorization createAuthorization() { - JwsHeader headers = JwsHeader.with(SignatureAlgorithm.RS256).build(); - // @formatter:off - JwtClaimsSet claimSet = JwtClaimsSet.builder() - .claims((claims) -> claims.putAll(createUserInfo().getClaims())) - .build(); - // @formatter:on - Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claimSet)); - - Instant now = Instant.now(); - Set scopes = new HashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, - OidcScopes.PHONE, OidcScopes.PROFILE)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), - now, now.plusSeconds(300), scopes); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .claims((claims) -> claims.putAll(createUserInfo().getClaims())) - .build(); - - return TestOAuth2Authorizations.authorization().accessToken(accessToken).token(idToken).build(); - } - - private static OidcUserInfo createUserInfo() { - // @formatter:off - return OidcUserInfo.builder() - .subject("user1") - .name("First Last") - .givenName("First") - .familyName("Last") - .middleName("Middle") - .nickname("User") - .preferredUsername("user") - .profile("https://example.com/user1") - .picture("https://example.com/user1.jpg") - .website("https://example.com") - .email("user1@example.com") - .emailVerified(true) - .gender("female") - .birthdate("1970-01-01") - .zoneinfo("Europe/Paris") - .locale("en-US") - .phoneNumber("+1 (604) 555-1234;ext=5678") - .phoneNumberVerified(false) - .claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) - .updatedAt("1970-01-01T00:00:00Z") - .build(); - // @formatter:on - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomUserInfoConfiguration extends AuthorizationServerConfiguration { - - @Bean - @Override - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .userInfoEndpoint((userInfo) -> - userInfo - .userInfoRequestConverter(authenticationConverter) - .userInfoRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .userInfoResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler) - .userInfoMapper(userInfoMapper))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - // @formatter:on - return http.build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithSecurityContextRepository - extends AuthorizationServerConfiguration { - - @Bean - @Override - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ) - .securityContext((securityContext) -> - securityContext.securityContextRepository(securityContextRepository)); - // @formatter:on - - return http.build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); - http - .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - .with(authorizationServerConfigurer, (authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - // @formatter:on - - return http.build(); - } - - @Bean - RegisteredClientRepository registeredClientRepository() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - return new InMemoryRegisteredClientRepository(registeredClient); - } - - @Bean - OAuth2AuthorizationService authorizationService() { - return new InMemoryOAuth2AuthorizationService(); - } - - @Bean - JWKSource jwkSource() { - return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - JwtEncoder jwtEncoder(JWKSource jwkSource) { - return new NimbusJwtEncoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java deleted file mode 100644 index e7c14db4e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.context; - -import java.util.function.Supplier; - -import org.springframework.lang.Nullable; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -/** - * @author Joe Grandja - */ -public class TestAuthorizationServerContext implements AuthorizationServerContext { - - private final AuthorizationServerSettings authorizationServerSettings; - - private final Supplier issuerSupplier; - - public TestAuthorizationServerContext(AuthorizationServerSettings authorizationServerSettings, - @Nullable Supplier issuerSupplier) { - this.authorizationServerSettings = authorizationServerSettings; - this.issuerSupplier = issuerSupplier; - } - - @Override - public String getIssuer() { - return (this.issuerSupplier != null) ? this.issuerSupplier.get() : getAuthorizationServerSettings().getIssuer(); - } - - @Override - public AuthorizationServerSettings getAuthorizationServerSettings() { - return this.authorizationServerSettings; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverterTests.java deleted file mode 100644 index f7f205d6f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverterTests.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.http.converter; - -import java.net.URL; -import java.util.Arrays; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; - -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 OAuth2AuthorizationServerMetadataHttpMessageConverter} - * - * @author Daniel Garnier-Moiroux - */ -public class OAuth2AuthorizationServerMetadataHttpMessageConverterTests { - - private final OAuth2AuthorizationServerMetadataHttpMessageConverter messageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter(); - - @Test - public void supportsWhenOAuth2AuthorizationServerMetadataThenTrue() { - assertThat(this.messageConverter.supports(OAuth2AuthorizationServerMetadata.class)).isTrue(); - } - - @Test - public void setAuthorizationServerMetadataParametersConverterWhenConverterIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setAuthorizationServerMetadataParametersConverter(null)); - } - - @Test - public void setAuthorizationServerMetadataConverterWhenConverterIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setAuthorizationServerMetadataConverter(null)); - } - - @Test - public void readInternalWhenRequiredParametersThenSuccess() throws Exception { - // @formatter:off - String authorizationServerMetadataResponse = "{\n" - + " \"issuer\": \"https://example.com\",\n" - + " \"authorization_endpoint\": \"https://example.com/oauth2/authorize\",\n" - + " \"token_endpoint\": \"https://example.com/oauth2/token\",\n" - + " \"response_types_supported\": [\"code\"]\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), - HttpStatus.OK); - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.messageConverter - .readInternal(OAuth2AuthorizationServerMetadata.class, response); - - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(new URL("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getTokenEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isNull(); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getScopes()).isNull(); - assertThat(authorizationServerMetadata.getGrantTypes()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isNull(); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isNull(); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull(); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull(); - } - - @Test - public void readInternalWhenValidParametersThenSuccess() throws Exception { - // @formatter:off - String authorizationServerMetadataResponse = "{\n" - + " \"issuer\": \"https://example.com\",\n" - + " \"authorization_endpoint\": \"https://example.com/oauth2/authorize\",\n" - + " \"token_endpoint\": \"https://example.com/oauth2/token\",\n" - + " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n" - + " \"jwks_uri\": \"https://example.com/oauth2/jwks\",\n" - + " \"scopes_supported\": [\"openid\"],\n" - + " \"response_types_supported\": [\"code\"],\n" - + " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n" - + " \"revocation_endpoint\": \"https://example.com/oauth2/revoke\",\n" - + " \"revocation_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n" - + " \"introspection_endpoint\": \"https://example.com/oauth2/introspect\",\n" - + " \"introspection_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n" - + " \"code_challenge_methods_supported\": [\"S256\"],\n" - + " \"custom_claim\": \"value\",\n" - + " \"custom_collection_claim\": [\"value1\", \"value2\"]\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), - HttpStatus.OK); - OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.messageConverter - .readInternal(OAuth2AuthorizationServerMetadata.class, response); - - assertThat(authorizationServerMetadata.getClaims()).hasSize(15); - assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(new URL("https://example.com")); - assertThat(authorizationServerMetadata.getAuthorizationEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/authorize")); - assertThat(authorizationServerMetadata.getTokenEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/token")); - assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(new URL("https://example.com/oauth2/jwks")); - assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid"); - assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code"); - assertThat(authorizationServerMetadata.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/revoke")); - assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/introspect")); - assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactly("S256"); - assertThat(authorizationServerMetadata.getClaimAsString("custom_claim")).isEqualTo("value"); - assertThat(authorizationServerMetadata.getClaimAsStringList("custom_collection_claim")) - .containsExactlyInAnyOrder("value1", "value2"); - } - - @Test - public void readInternalWhenFailingConverterThenThrowException() { - String errorMessage = "this is not a valid converter"; - this.messageConverter.setAuthorizationServerMetadataConverter((source) -> { - throw new RuntimeException(errorMessage); - }); - MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerMetadata.class, response)) - .withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Metadata") - .withMessageContaining(errorMessage); - } - - @Test - public void readInternalWhenInvalidOAuth2AuthorizationServerMetadataThenThrowException() { - String authorizationServerMetadataResponse = "{ \"issuer\": null }"; - MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), - HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerMetadata.class, response)) - .withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Metadata") - .withMessageContaining("issuer cannot be null"); - } - - @Test - public void writeInternalWhenOAuth2AuthorizationServerMetadataThenSuccess() { - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .jwkSetUrl("https://example.com/oauth2/jwks") - .scope("openid") - .responseType("code") - .grantType("authorization_code") - .grantType("client_credentials") - .tokenRevocationEndpoint("https://example.com/oauth2/revoke") - .tokenRevocationEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .tokenIntrospectionEndpoint("https://example.com/oauth2/introspect") - .tokenIntrospectionEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .codeChallengeMethod("S256") - .claim("custom_claim", "value") - .claim("custom_collection_claim", Arrays.asList("value1", "value2")) - .build(); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - this.messageConverter.writeInternal(authorizationServerMetadata, outputMessage); - - String authorizationServerMetadataResponse = outputMessage.getBodyAsString(); - assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"authorization_endpoint\":\"https://example.com/oauth2/authorize\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"token_endpoint\":\"https://example.com/oauth2/token\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/jwks\""); - assertThat(authorizationServerMetadataResponse).contains("\"scopes_supported\":[\"openid\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]"); - assertThat(authorizationServerMetadataResponse) - .contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]"); - assertThat(authorizationServerMetadataResponse) - .contains("\"revocation_endpoint\":\"https://example.com/oauth2/revoke\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\"]"); - assertThat(authorizationServerMetadataResponse) - .contains("\"introspection_endpoint\":\"https://example.com/oauth2/introspect\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"S256\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"custom_claim\":\"value\""); - assertThat(authorizationServerMetadataResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]"); - } - - @Test - public void writeInternalWhenWriteFailsThenThrowException() { - String errorMessage = "this is not a valid converter"; - Converter> failingConverter = (source) -> { - throw new RuntimeException(errorMessage); - }; - this.messageConverter.setAuthorizationServerMetadataParametersConverter(failingConverter); - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .responseType("code") - .build(); - - assertThatExceptionOfType(HttpMessageNotWritableException.class) - .isThrownBy(() -> this.messageConverter.writeInternal(authorizationServerMetadata, outputMessage)) - .withMessageContaining("An error occurred writing the OAuth 2.0 Authorization Server Metadata") - .withMessageContaining(errorMessage); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverterTests.java deleted file mode 100644 index 3866f0cb9..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverterTests.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.http.converter; - -import java.net.URL; -import java.time.Instant; -import java.util.Arrays; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; - -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.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenIntrospectionHttpMessageConverter} - * - * @author Gerardo Roza - * @author Joe Grandja - */ -public class OAuth2TokenIntrospectionHttpMessageConverterTests { - - private final OAuth2TokenIntrospectionHttpMessageConverter messageConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); - - @Test - public void supportsWhenOAuth2TokenIntrospectionThenTrue() { - assertThat(this.messageConverter.supports(OAuth2TokenIntrospection.class)).isTrue(); - } - - @Test - public void setTokenIntrospectionParametersConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setTokenIntrospectionParametersConverter(null)); - } - - @Test - public void setTokenIntrospectionConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setTokenIntrospectionConverter(null)); - } - - @Test - public void readInternalWhenValidParametersThenSuccess() throws Exception { - // @formatter:off - String tokenIntrospectionResponseBody = "{\n" - + " \"active\": true,\n" - + " \"client_id\": \"clientId1\",\n" - + " \"username\": \"username1\",\n" - + " \"iat\": 1607633867,\n" - + " \"exp\": 1607637467,\n" - + " \"scope\": \"scope1 scope2\",\n" - + " \"token_type\": \"Bearer\",\n" - + " \"nbf\": 1607633867,\n" - + " \"sub\": \"subject1\",\n" - + " \"aud\": [\"audience1\", \"audience2\"],\n" - + " \"iss\": \"https://example.com/issuer1\",\n" - + " \"jti\": \"jwtId1\"\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(tokenIntrospectionResponseBody.getBytes(), - HttpStatus.OK); - OAuth2TokenIntrospection tokenIntrospectionResponse = this.messageConverter - .readInternal(OAuth2TokenIntrospection.class, response); - - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo("clientId1"); - assertThat(tokenIntrospectionResponse.getUsername()).isEqualTo("username1"); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isEqualTo(Instant.ofEpochSecond(1607633867L)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isEqualTo(Instant.ofEpochSecond(1607637467L)); - assertThat(tokenIntrospectionResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(Arrays.asList("scope1", "scope2")); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo("Bearer"); - assertThat(tokenIntrospectionResponse.getNotBefore()).isEqualTo(Instant.ofEpochSecond(1607633867L)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo("subject1"); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(Arrays.asList("audience1", "audience2")); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(new URL("https://example.com/issuer1")); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo("jwtId1"); - } - - @Test - public void readInternalWhenFailingConverterThenThrowException() { - String errorMessage = "this is not a valid converter"; - this.messageConverter.setTokenIntrospectionConverter((source) -> { - throw new RuntimeException(errorMessage); - }); - MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OAuth2TokenIntrospection.class, response)) - .withMessageContaining("An error occurred reading the Token Introspection Response") - .withMessageContaining(errorMessage); - } - - @Test - public void writeInternalWhenTokenIntrospectionThenSuccess() { - // @formatter:off - OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true) - .clientId("clientId1") - .username("username1") - .issuedAt(Instant.ofEpochSecond(1607633867)) - .expiresAt(Instant.ofEpochSecond(1607637467)) - .scope("scope1 scope2") - .tokenType(TokenType.BEARER.getValue()) - .notBefore(Instant.ofEpochSecond(1607633867)) - .subject("subject1") - .audience("audience1") - .audience("audience2") - .issuer("https://example.com/issuer1") - .id("jwtId1") - .build(); - // @formatter:on - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - this.messageConverter.writeInternal(tokenClaims, outputMessage); - - String tokenIntrospectionResponse = outputMessage.getBodyAsString(); - assertThat(tokenIntrospectionResponse).contains("\"active\":true"); - assertThat(tokenIntrospectionResponse).contains("\"client_id\":\"clientId1\""); - assertThat(tokenIntrospectionResponse).contains("\"username\":\"username1\""); - assertThat(tokenIntrospectionResponse).contains("\"iat\":1607633867"); - assertThat(tokenIntrospectionResponse).contains("\"exp\":1607637467"); - assertThat(tokenIntrospectionResponse).contains("\"scope\":\"scope1 scope2\""); - assertThat(tokenIntrospectionResponse).contains("\"token_type\":\"Bearer\""); - assertThat(tokenIntrospectionResponse).contains("\"nbf\":1607633867"); - assertThat(tokenIntrospectionResponse).contains("\"sub\":\"subject1\""); - assertThat(tokenIntrospectionResponse).contains("\"aud\":[\"audience1\",\"audience2\"]"); - assertThat(tokenIntrospectionResponse).contains("\"iss\":\"https://example.com/issuer1\""); - assertThat(tokenIntrospectionResponse).contains("\"jti\":\"jwtId1\""); - } - - @Test - public void writeInternalWhenWriteFailsThenThrowsException() { - String errorMessage = "this is not a valid converter"; - Converter> failingConverter = (source) -> { - throw new RuntimeException(errorMessage); - }; - this.messageConverter.setTokenIntrospectionParametersConverter(failingConverter); - - OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder().build(); - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - assertThatThrownBy(() -> this.messageConverter.writeInternal(tokenClaims, outputMessage)) - .isInstanceOf(HttpMessageNotWritableException.class) - .hasMessageContaining("An error occurred writing the Token Introspection Response") - .hasMessageContaining(errorMessage); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2ModuleTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2ModuleTests.java deleted file mode 100644 index 03be1e4dd..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2ModuleTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.jackson2; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2AuthorizationServerJackson2Module}. - * - * @author Steve Riesenberg - */ -public class OAuth2AuthorizationServerJackson2ModuleTests { - - private static final TypeReference> STRING_OBJECT_MAP = new TypeReference<>() { - }; - - private static final TypeReference> STRING_SET = new TypeReference<>() { - }; - - private static final TypeReference STRING_ARRAY = new TypeReference<>() { - }; - - private ObjectMapper objectMapper; - - @BeforeEach - public void setup() { - this.objectMapper = new ObjectMapper(); - this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); - } - - @Test - public void readValueWhenUnmodifiableMapThenSuccess() throws Exception { - Map map = Collections.unmodifiableMap(new HashMap<>(Collections.singletonMap("key", "value"))); - String json = this.objectMapper.writeValueAsString(map); - assertThat(this.objectMapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(map); - } - - @Test - public void readValueWhenHashSetThenSuccess() throws Exception { - Set set = new HashSet<>(Arrays.asList("one", "two")); - String json = this.objectMapper.writeValueAsString(set); - assertThat(this.objectMapper.readValue(json, STRING_SET)).isEqualTo(set); - } - - // gh-457 - @Test - public void readValueWhenLinkedHashSetThenSuccess() throws Exception { - Set set = new LinkedHashSet<>(Arrays.asList("one", "two")); - String json = this.objectMapper.writeValueAsString(set); - assertThat(this.objectMapper.readValue(json, STRING_SET)).isEqualTo(set); - } - - // gh-1666 - @Test - public void readValueWhenStringArrayThenSuccess() throws Exception { - String[] array = new String[] { "one", "two" }; - String json = this.objectMapper.writeValueAsString(array); - assertThat(this.objectMapper.readValue(json, STRING_ARRAY)).isEqualTo(array); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/TestingAuthenticationTokenMixin.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/TestingAuthenticationTokenMixin.java deleted file mode 100644 index a4494a7c0..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/jackson2/TestingAuthenticationTokenMixin.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2020-2021 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.server.authorization.jackson2; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; - -/** - * This mixin class is used to serialize/deserialize {@link TestingAuthenticationToken}. - * - * @author Steve Riesenberg - * @since 0.1.2 - * @see TestingAuthenticationToken - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, - isGetterVisibility = JsonAutoDetect.Visibility.NONE) -@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true) -public class TestingAuthenticationTokenMixin { - - @JsonCreator - TestingAuthenticationTokenMixin(@JsonProperty("principal") Object principal, - @JsonProperty("credentials") Object credentials, - @JsonProperty("authorities") List authorities) { - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistrationTests.java deleted file mode 100644 index 96ce6ceff..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientRegistrationTests.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import java.net.URL; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OidcClientRegistration}. - * - * @author Ovidiu Popa - * @author Joe Grandja - */ -public class OidcClientRegistrationTests { - - // @formatter:off - private final OidcClientRegistration.Builder minimalBuilder = - OidcClientRegistration.builder() - .redirectUri("https://client.example.com"); - // @formatter:on - - @Test - public void buildWhenAllClaimsProvidedThenCreated() throws Exception { - // @formatter:off - Instant clientIdIssuedAt = Instant.now(); - Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientId("client-id") - .clientIdIssuedAt(clientIdIssuedAt) - .clientSecret("client-secret") - .clientSecretExpiresAt(clientSecretExpiresAt) - .clientName("client-name") - .redirectUri("https://client.example.com") - .postLogoutRedirectUri("https://client.example.com/oidc-post-logout") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName()) - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) - .scope("scope1") - .scope("scope2") - .jwkSetUrl("https://client.example.com/jwks") - .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .registrationAccessToken("registration-access-token") - .registrationClientUrl("https://auth-server.com/connect/register?client_id=1") - .claim("a-claim", "a-value") - .build(); - // @formatter:on - - assertThat(clientRegistration.getClientId()).isEqualTo("client-id"); - assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt); - assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret"); - assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt); - assertThat(clientRegistration.getClientName()).isEqualTo("client-name"); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - assertThat(clientRegistration.getPostLogoutRedirectUris()) - .containsOnly("https://client.example.com/oidc-post-logout"); - assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(MacAlgorithm.HS256.getName()); - assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(clientRegistration.getResponseTypes()).containsOnly("code"); - assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); - assertThat(clientRegistration.getJwkSetUrl()).isEqualTo(new URL("https://client.example.com/jwks")); - assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256"); - assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token"); - assertThat(clientRegistration.getRegistrationClientUrl().toString()) - .isEqualTo("https://auth-server.com/connect/register?client_id=1"); - assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); - } - - @Test - public void buildWhenOnlyRequiredClaimsProvidedThenCreated() { - OidcClientRegistration clientRegistration = this.minimalBuilder.build(); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - } - - @Test - public void withClaimsWhenClaimsProvidedThenCreated() throws Exception { - Instant clientIdIssuedAt = Instant.now(); - Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS); - HashMap claims = new HashMap<>(); - claims.put(OidcClientMetadataClaimNames.CLIENT_ID, "client-id"); - claims.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, clientIdIssuedAt); - claims.put(OidcClientMetadataClaimNames.CLIENT_SECRET, "client-secret"); - claims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt); - claims.put(OidcClientMetadataClaimNames.CLIENT_NAME, "client-name"); - claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.singletonList("https://client.example.com")); - claims.put(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, - Collections.singletonList("https://client.example.com/oidc-post-logout")); - claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, - ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, MacAlgorithm.HS256.getName()); - claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, - Arrays.asList(AuthorizationGrantType.AUTHORIZATION_CODE.getValue(), - AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())); - claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, Collections.singletonList("code")); - claims.put(OidcClientMetadataClaimNames.SCOPE, Arrays.asList("scope1", "scope2")); - claims.put(OidcClientMetadataClaimNames.JWKS_URI, "https://client.example.com/jwks"); - claims.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, SignatureAlgorithm.RS256.getName()); - claims.put(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, "registration-access-token"); - claims.put(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, - "https://auth-server.com/connect/register?client_id=1"); - claims.put("a-claim", "a-value"); - - OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(claims).build(); - - assertThat(clientRegistration.getClientId()).isEqualTo("client-id"); - assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt); - assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret"); - assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt); - assertThat(clientRegistration.getClientName()).isEqualTo("client-name"); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - assertThat(clientRegistration.getPostLogoutRedirectUris()) - .containsOnly("https://client.example.com/oidc-post-logout"); - assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(MacAlgorithm.HS256.getName()); - assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(clientRegistration.getResponseTypes()).containsOnly("code"); - assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); - assertThat(clientRegistration.getJwkSetUrl()).isEqualTo(new URL("https://client.example.com/jwks")); - assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256"); - assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token"); - assertThat(clientRegistration.getRegistrationClientUrl().toString()) - .isEqualTo("https://auth-server.com/connect/register?client_id=1"); - assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); - } - - @Test - public void withClaimsWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OidcClientRegistration.withClaims(null)) - .withMessage("claims cannot be empty"); - } - - @Test - public void withClaimsWhenEmptyThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OidcClientRegistration.withClaims(Collections.emptyMap())) - .withMessage("claims cannot be empty"); - } - - @Test - public void buildWhenMissingClientIdThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.clientIdIssuedAt(Instant.now()); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("client_id cannot be null"); - } - - @Test - public void buildWhenClientSecretAndMissingClientIdThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.clientSecret("client-secret"); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("client_id cannot be null"); - } - - @Test - public void buildWhenClientIdIssuedAtNotInstantThenThrowIllegalArgumentException() { - // @formatter:off - OidcClientRegistration.Builder builder = this.minimalBuilder - .clientId("client-id") - .claim(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, "clientIdIssuedAt"); - // @formatter:on - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("client_id_issued_at must be of type Instant"); - } - - @Test - public void buildWhenMissingClientSecretThenThrowIllegalArgumentException() { - // @formatter:off - OidcClientRegistration.Builder builder = this.minimalBuilder - .clientId("client-id") - .clientIdIssuedAt(Instant.now()) - .clientSecretExpiresAt(Instant.now().plus(30, ChronoUnit.DAYS)); - // @formatter:on - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("client_secret cannot be null"); - } - - @Test - public void buildWhenClientSecretExpiresAtNotInstantThenThrowIllegalArgumentException() { - // @formatter:off - OidcClientRegistration.Builder builder = this.minimalBuilder - .clientId("client-id") - .clientIdIssuedAt(Instant.now()) - .clientSecret("client-secret") - .claim(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, "clientSecretExpiresAt"); - // @formatter:on - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("client_secret_expires_at must be of type Instant"); - } - - @Test - public void buildWhenMissingRedirectUrisThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = OidcClientRegistration.builder().clientName("client-name"); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("redirect_uris cannot be null"); - } - - @Test - public void buildWhenRedirectUrisNotListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .claim(OidcClientMetadataClaimNames.REDIRECT_URIS, "redirectUris"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("redirect_uris must be of type List"); - } - - @Test - public void buildWhenRedirectUrisEmptyListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .claim(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.emptyList()); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("redirect_uris cannot be empty"); - } - - @Test - public void buildWhenRedirectUrisAddingOrRemovingThenCorrectValues() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .redirectUri("https://client1.example.com") - .redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://client2.example.com"); - }) - .build(); - // @formatter:on - - assertThat(clientRegistration.getRedirectUris()).containsExactly("https://client2.example.com"); - } - - @Test - public void buildWhenPostLogoutRedirectUrisNotListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder - .claim(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, "postLogoutRedirectUris"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("post_logout_redirect_uris must be of type List"); - } - - @Test - public void buildWhenPostLogoutRedirectUrisEmptyListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder - .claim(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS, Collections.emptyList()); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("post_logout_redirect_uris cannot be empty"); - } - - @Test - public void buildWhenPostLogoutRedirectUrisAddingOrRemovingThenCorrectValues() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .postLogoutRedirectUri("https://client1.example.com/oidc-post-logout") - .postLogoutRedirectUris((postLogoutRedirectUris) -> { - postLogoutRedirectUris.clear(); - postLogoutRedirectUris.add("https://client2.example.com/oidc-post-logout"); - }) - .build(); - // @formatter:on - - assertThat(clientRegistration.getPostLogoutRedirectUris()) - .containsExactly("https://client2.example.com/oidc-post-logout"); - } - - @Test - public void buildWhenGrantTypesNotListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.GRANT_TYPES, - "grantTypes"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("grant_types must be of type List"); - } - - @Test - public void buildWhenGrantTypesEmptyListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.GRANT_TYPES, - Collections.emptyList()); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("grant_types cannot be empty"); - } - - @Test - public void buildWhenGrantTypesAddingOrRemovingThenCorrectValues() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .grantType("authorization_code") - .grantTypes((grantTypes) -> { - grantTypes.clear(); - grantTypes.add("client_credentials"); - }) - .build(); - // @formatter:on - - assertThat(clientRegistration.getGrantTypes()).containsExactly("client_credentials"); - } - - @Test - public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.RESPONSE_TYPES, - "responseTypes"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("response_types must be of type List"); - } - - @Test - public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.RESPONSE_TYPES, - Collections.emptyList()); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("response_types cannot be empty"); - } - - @Test - public void buildWhenResponseTypesAddingOrRemovingThenCorrectValues() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .responseType("token") - .responseTypes((responseTypes) -> { - responseTypes.clear(); - responseTypes.add("code"); - }) - .build(); - // @formatter:on - - assertThat(clientRegistration.getResponseTypes()).containsExactly("code"); - } - - @Test - public void buildWhenScopesNotListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.SCOPE, - "scopes"); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("scope must be of type List"); - } - - @Test - public void buildWhenScopesEmptyListThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.SCOPE, - Collections.emptyList()); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("scope cannot be empty"); - } - - @Test - public void buildWhenScopesAddingOrRemovingThenCorrectValues() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .scope("should-be-removed") - .scopes((scopes) -> { - scopes.clear(); - scopes.add("scope1"); - }) - .build(); - // @formatter:on - - assertThat(clientRegistration.getScopes()).containsExactly("scope1"); - } - - @Test - public void buildWhenJwksUriNotUrlThenThrowIllegalArgumentException() { - OidcClientRegistration.Builder builder = this.minimalBuilder.claim(OidcClientMetadataClaimNames.JWKS_URI, - "not an url"); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("jwksUri must be a valid URL"); - } - - @Test - public void claimWhenNameNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OidcClientRegistration.builder().claim(null, "claim-value")) - .withMessage("name cannot be empty"); - } - - @Test - public void claimWhenValueNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OidcClientRegistration.builder().claim("claim-name", null)) - .withMessage("value cannot be null"); - } - - @Test - public void claimsWhenRemovingClaimThenNotPresent() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .claim("claim-name", "claim-value") - .claims((claims) -> claims.remove("claim-name")) - .build(); - // @formatter:on - - assertThat(clientRegistration.hasClaim("claim-name")).isFalse(); - } - - @Test - public void claimsWhenAddingClaimThenPresent() { - // @formatter:off - OidcClientRegistration clientRegistration = this.minimalBuilder - .claim("claim-name", "claim-value") - .build(); - // @formatter:on - - assertThat(clientRegistration.hasClaim("claim-name")).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfigurationTests.java deleted file mode 100644 index cd6e33bc5..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderConfigurationTests.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OidcProviderConfiguration}. - * - * @author Daniel Garnier-Moiroux - */ -public class OidcProviderConfigurationTests { - - private final OidcProviderConfiguration.Builder minimalConfigurationBuilder = OidcProviderConfiguration.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .jwkSetUrl("https://example.com/oauth2/jwks") - .scope("openid") - .responseType("code") - .subjectType("public") - .idTokenSigningAlgorithm("RS256"); - - @Test - public void buildWhenAllRequiredClaimsAndAdditionalClaimsThenCreated() { - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .jwkSetUrl("https://example.com/oauth2/jwks") - .scope("openid") - .responseType("code") - .grantType("authorization_code") - .grantType("client_credentials") - .subjectType("public") - .idTokenSigningAlgorithm("RS256") - .userInfoEndpoint("https://example.com/userinfo") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .clientRegistrationEndpoint("https://example.com/connect/register") - .endSessionEndpoint("https://example.com/connect/logout") - .claim("a-claim", "a-value") - .build(); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getScopes()).containsExactly("openid"); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/userinfo")); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(providerConfiguration.getClientRegistrationEndpoint()) - .isEqualTo(url("https://example.com/connect/register")); - assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/connect/logout")); - assertThat(providerConfiguration.getClaim("a-claim")).isEqualTo("a-value"); - } - - @Test - public void buildWhenOnlyRequiredClaimsThenCreated() { - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .jwkSetUrl("https://example.com/oauth2/jwks") - .scope("openid") - .responseType("code") - .subjectType("public") - .idTokenSigningAlgorithm("RS256") - .build(); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getScopes()).containsExactly("openid"); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getGrantTypes()).isNull(); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull(); - } - - @Test - public void buildWhenClaimsProvidedThenCreated() { - Map claims = new HashMap<>(); - claims.put(OidcProviderMetadataClaimNames.ISSUER, "https://example.com"); - claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, "https://example.com/oauth2/authorize"); - claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/oauth2/token"); - claims.put(OidcProviderMetadataClaimNames.JWKS_URI, "https://example.com/oauth2/jwks"); - claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid")); - claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code")); - claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public")); - claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, - Collections.singletonList("RS256")); - claims.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, "https://example.com/userinfo"); - claims.put(OidcProviderMetadataClaimNames.REGISTRATION_ENDPOINT, "https://example.com/connect/register"); - claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, "https://example.com/connect/logout"); - claims.put("some-claim", "some-value"); - - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build(); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getScopes()).containsExactly("openid"); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getGrantTypes()).isNull(); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/userinfo")); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(providerConfiguration.getClientRegistrationEndpoint()) - .isEqualTo(url("https://example.com/connect/register")); - assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/connect/logout")); - assertThat(providerConfiguration.getClaim("some-claim")).isEqualTo("some-value"); - } - - @Test - public void buildWhenClaimsProvidedWithUrlsThenCreated() { - Map claims = new HashMap<>(); - claims.put(OidcProviderMetadataClaimNames.ISSUER, url("https://example.com")); - claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, url("https://example.com/oauth2/authorize")); - claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/oauth2/token")); - claims.put(OidcProviderMetadataClaimNames.JWKS_URI, url("https://example.com/oauth2/jwks")); - claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid")); - claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code")); - claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public")); - claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, - Collections.singletonList("RS256")); - claims.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, url("https://example.com/userinfo")); - claims.put(OidcProviderMetadataClaimNames.REGISTRATION_ENDPOINT, url("https://example.com/connect/register")); - claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, url("https://example.com/connect/logout")); - claims.put("some-claim", "some-value"); - - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build(); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(url("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getScopes()).containsExactly("openid"); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getGrantTypes()).isNull(); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(url("https://example.com/userinfo")); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull(); - assertThat(providerConfiguration.getClientRegistrationEndpoint()) - .isEqualTo(url("https://example.com/connect/register")); - assertThat(providerConfiguration.getEndSessionEndpoint()).isEqualTo(url("https://example.com/connect/logout")); - assertThat(providerConfiguration.getClaim("some-claim")).isEqualTo("some-value"); - } - - @Test - public void withClaimsWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OidcProviderConfiguration.withClaims(null)) - .isInstanceOf(IllegalArgumentException.class) - .withMessage("claims cannot be empty"); - } - - @Test - public void withClaimsWhenMissingRequiredClaimsThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OidcProviderConfiguration.withClaims(Collections.emptyMap())) - .withMessage("claims cannot be empty"); - } - - @Test - public void buildWhenCalledTwiceThenGeneratesTwoConfigurations() { - OidcProviderConfiguration first = this.minimalConfigurationBuilder.grantType("client_credentials").build(); - - OidcProviderConfiguration second = this.minimalConfigurationBuilder.claims((claims) -> { - List newGrantTypes = new ArrayList<>(); - newGrantTypes.add("authorization_code"); - newGrantTypes.add("custom_grant"); - claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes); - }).build(); - - assertThat(first.getGrantTypes()).containsExactly("client_credentials"); - assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant"); - } - - @Test - public void buildWhenMissingIssuerThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.ISSUER)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("issuer cannot be null"); - } - - @Test - public void buildWhenIssuerNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.ISSUER, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("issuer must be a valid URL"); - } - - @Test - public void buildWhenMissingAuthorizationEndpointThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT)); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("authorizationEndpoint cannot be null"); - } - - @Test - public void buildWhenAuthorizationEndpointNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("authorizationEndpoint must be a valid URL"); - } - - @Test - public void buildWhenMissingTokenEndpointThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("tokenEndpoint cannot be null"); - } - - @Test - public void buildWhenTokenEndpointNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("tokenEndpoint must be a valid URL"); - } - - @Test - public void buildWhenMissingJwksUriThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.JWKS_URI)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("jwksUri cannot be null"); - } - - @Test - public void buildWhenJwksUriNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.JWKS_URI, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageStartingWith("jwksUri must be a valid URL"); - } - - @Test - public void buildWhenMissingResponseTypesThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("responseTypes cannot be null"); - } - - @Test - public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, "code"); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("responseTypes must be of type List"); - } - - @Test - public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.emptyList()); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("responseTypes cannot be empty"); - } - - @Test - public void buildWhenMissingSubjectTypesThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED)); - - assertThatIllegalArgumentException().isThrownBy(builder::build).withMessage("subjectTypes cannot be null"); - } - - @Test - public void buildWhenSubjectTypesNotListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, "public"); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("subjectTypes must be of type List"); - } - - @Test - public void buildWhenSubjectTypesEmptyListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.emptyList()); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("subjectTypes cannot be empty"); - } - - @Test - public void buildWhenMissingIdTokenSigningAlgorithmsThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED)); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("idTokenSigningAlgorithms cannot be null"); - } - - @Test - public void buildWhenIdTokenSigningAlgorithmsNotListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, "RS256"); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("idTokenSigningAlgorithms must be of type List"); - } - - @Test - public void buildWhenIdTokenSigningAlgorithmsEmptyListThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder.claims((claims) -> { - claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); - claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.emptyList()); - }); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessageContaining("idTokenSigningAlgorithms cannot be empty"); - } - - @Test - public void buildWhenUserInfoEndpointNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("userInfoEndpoint must be a valid URL"); - } - - @Test - public void buildWhenClientRegistrationEndpointNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.REGISTRATION_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("clientRegistrationEndpoint must be a valid URL"); - } - - @Test - public void buildWhenEndSessionEndpointNotUrlThenThrowIllegalArgumentException() { - OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder - .claims((claims) -> claims.put(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT, "not an url")); - - assertThatIllegalArgumentException().isThrownBy(builder::build) - .withMessage("endSessionEndpoint must be a valid URL"); - } - - @Test - public void responseTypesWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.responseType("should-be-removed") - .responseTypes((responseTypes) -> { - responseTypes.clear(); - responseTypes.add("some-response-type"); - }) - .build(); - - assertThat(configuration.getResponseTypes()).containsExactly("some-response-type"); - } - - @Test - public void responseTypesWhenNotPresentAndAddingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED)) - .responseTypes((responseTypes) -> responseTypes.add("some-response-type")) - .build(); - - assertThat(configuration.getResponseTypes()).containsExactly("some-response-type"); - } - - @Test - public void subjectTypesWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.subjectType("should-be-removed") - .subjectTypes((subjectTypes) -> { - subjectTypes.clear(); - subjectTypes.add("some-subject-type"); - }) - .build(); - - assertThat(configuration.getSubjectTypes()).containsExactly("some-subject-type"); - } - - @Test - public void idTokenSigningAlgorithmsWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder - .idTokenSigningAlgorithm("should-be-removed") - .idTokenSigningAlgorithms((signingAlgorithms) -> { - signingAlgorithms.clear(); - signingAlgorithms.add("ES256"); - }) - .build(); - - assertThat(configuration.getIdTokenSigningAlgorithms()).containsExactly("ES256"); - } - - @Test - public void scopesWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.scope("should-be-removed") - .scopes((scopes) -> { - scopes.clear(); - scopes.add("some-scope"); - }) - .build(); - - assertThat(configuration.getScopes()).containsExactly("some-scope"); - } - - @Test - public void grantTypesWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.grantType("should-be-removed") - .grantTypes((grantTypes) -> { - grantTypes.clear(); - grantTypes.add("some-grant-type"); - }) - .build(); - - assertThat(configuration.getGrantTypes()).containsExactly("some-grant-type"); - } - - @Test - public void tokenEndpointAuthenticationMethodsWhenAddingOrRemovingThenCorrectValues() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder - .tokenEndpointAuthenticationMethod("should-be-removed") - .tokenEndpointAuthenticationMethods((authMethods) -> { - authMethods.clear(); - authMethods.add("some-authentication-method"); - }) - .build(); - - assertThat(configuration.getTokenEndpointAuthenticationMethods()).containsExactly("some-authentication-method"); - } - - @Test - public void claimWhenNameIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> OidcProviderConfiguration.builder().claim(null, "value")) - .withMessage("name cannot be empty"); - } - - @Test - public void claimWhenValueIsNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> OidcProviderConfiguration.builder().claim("claim-name", null)) - .withMessage("value cannot be null"); - } - - @Test - public void claimsWhenRemovingClaimThenNotPresent() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.grantType("some-grant-type") - .claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED)) - .build(); - assertThat(configuration.getGrantTypes()).isNull(); - } - - @Test - public void claimsWhenAddingClaimThenPresent() { - OidcProviderConfiguration configuration = this.minimalConfigurationBuilder.claim("claim-name", "claim-value") - .build(); - assertThat(configuration.hasClaim("claim-name")).isTrue(); - } - - private static URL url(String urlString) { - try { - return new URL(urlString); - } - catch (Exception ex) { - throw new IllegalArgumentException("urlString must be a valid URL and valid URI"); - } - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java deleted file mode 100644 index 1772f6b55..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProviderTests.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.TestJwsHeaders; -import org.springframework.security.oauth2.jwt.TestJwtClaimsSets; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OidcClientConfigurationAuthenticationProvider}. - * - * @author Ovidiu Popa - * @author Joe Grandja - */ -public class OidcClientConfigurationAuthenticationProviderTests { - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private AuthorizationServerSettings authorizationServerSettings; - - private OidcClientConfigurationAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null)); - this.authenticationProvider = new OidcClientConfigurationAuthenticationProvider(this.registeredClientRepository, - this.authorizationService); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(null, this.authorizationService)) - .withMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientConfigurationAuthenticationProvider(this.registeredClientRepository, null)) - .withMessage("authorizationService cannot be null"); - } - - @Test - public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue(); - } - - @Test - public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null)) - .withMessage("clientRegistrationConverter cannot be null"); - } - - @Test - public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, "client-id"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientConfiguration()); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, "client-id"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } - - @Test - public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, "client-id"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .invalidate(jwtAccessToken) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwt(Collections.singleton("unauthorized.scope")); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1"))); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - verify(this.registeredClientRepository).findByClientId(eq(registeredClient.getClientId())); - } - - @Test - public void authenticateWhenClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient() - .id("registration-2") - .clientId("client-2") - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - verify(this.registeredClientRepository).findByClientId(eq(registeredClient.getClientId())); - } - - @Test - public void authenticateWhenValidAccessTokenThenReturnClientRegistration() { - Jwt jwt = createJwtClientConfiguration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethods((clientAuthenticationMethods) -> { - clientAuthenticationMethods.clear(); - clientAuthenticationMethods.add(ClientAuthenticationMethod.PRIVATE_KEY_JWT); - }) - .clientSettings(ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS512) - .jwkSetUrl("https://client.example.com/jwks") - .build()) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .willReturn(registeredClient); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, registeredClient.getClientId()); - - OidcClientRegistrationAuthenticationToken authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - verify(this.registeredClientRepository).findByClientId(eq(registeredClient.getClientId())); - - // verify that the "registration" access token is not invalidated after it is used - verify(this.authorizationService, never()).save(eq(authorization)); - assertThat(authorization.getAccessToken().isInvalidated()).isFalse(); - - OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration(); - assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt()); - assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret()); - assertThat(clientRegistrationResult.getClientSecretExpiresAt()) - .isEqualTo(registeredClient.getClientSecretExpiresAt()); - assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName()); - assertThat(clientRegistrationResult.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris()); - - List grantTypes = new ArrayList<>(); - registeredClient.getAuthorizationGrantTypes() - .forEach((authorizationGrantType) -> grantTypes.add(authorizationGrantType.getValue())); - assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes); - - assertThat(clientRegistrationResult.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientRegistrationResult.getScopes()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getScopes()); - assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod()) - .isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()); - assertThat(clientRegistrationResult.getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm().getName()); - assertThat(clientRegistrationResult.getJwkSetUrl().toString()) - .isEqualTo(registeredClient.getClientSettings().getJwkSetUrl()); - assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String expectedRegistrationClientUrl = UriComponentsBuilder - .fromUriString(authorizationServerContext.getIssuer()) - .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint()) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .toUriString(); - - assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()) - .isEqualTo(expectedRegistrationClientUrl); - assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull(); - } - - private static Jwt createJwtClientConfiguration() { - return createJwt(Collections.singleton("client.read")); - } - - private static Jwt createJwt(Set scopes) { - // @formatter:off - JwsHeader jwsHeader = TestJwsHeaders.jwsHeader() - .build(); - JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet() - .claim(OAuth2ParameterNames.SCOPE, scopes) - .build(); - Jwt jwt = Jwt.withTokenValue("jwt-access-token") - .headers((headers) -> headers.putAll(jwsHeader.getHeaders())) - .claims((claims) -> claims.putAll(jwtClaimsSet.getClaims())) - .build(); - // @formatter:on - return jwt; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java deleted file mode 100644 index bb95c02e5..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java +++ /dev/null @@ -1,791 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -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.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.TestJwsHeaders; -import org.springframework.security.oauth2.jwt.TestJwtClaimsSets; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcClientRegistrationAuthenticationProvider}. - * - * @author Ovidiu Popa - * @author Joe Grandja - */ -public class OidcClientRegistrationAuthenticationProviderTests { - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private JwtEncoder jwtEncoder; - - private OAuth2TokenGenerator tokenGenerator; - - private PasswordEncoder passwordEncoder; - - private AuthorizationServerSettings authorizationServerSettings; - - private OidcClientRegistrationAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.jwtEncoder = mock(JwtEncoder.class); - JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder); - this.tokenGenerator = spy(new OAuth2TokenGenerator() { - @Override - public Jwt generate(OAuth2TokenContext context) { - return jwtGenerator.generate(context); - } - }); - this.passwordEncoder = spy(new PasswordEncoder() { - @Override - public String encode(CharSequence rawPassword) { - return NoOpPasswordEncoder.getInstance().encode(rawPassword); - } - - @Override - public boolean matches(CharSequence rawPassword, String encodedPassword) { - return NoOpPasswordEncoder.getInstance().matches(rawPassword, encodedPassword); - } - }); - this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null)); - this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, this.tokenGenerator); - this.authenticationProvider.setPasswordEncoder(this.passwordEncoder); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService, - this.tokenGenerator)) - .withMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null, - this.tokenGenerator)) - .withMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, null)) - .withMessage("tokenGenerator cannot be null"); - } - - @Test - public void setRegisteredClientConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setRegisteredClientConverter(null)) - .withMessage("registeredClientConverter cannot be null"); - } - - @Test - public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.authenticationProvider.setClientRegistrationConverter(null)) - .withMessage("clientRegistrationConverter cannot be null"); - } - - @Test - public void setPasswordEncoderWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setPasswordEncoder(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("passwordEncoder cannot be null"); - } - - @Test - public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientRegistration()); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } - - @Test - public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .invalidate(jwtAccessToken) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwt(Collections.singleton("unauthorized.scope")); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope")); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.create", "scope1"))); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create", "SCOPE_scope1")); - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("invalid uri") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REDIRECT_URI); - assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.REDIRECT_URIS); - }); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com#fragment") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REDIRECT_URI); - assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.REDIRECT_URIS); - }); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenInvalidPostLogoutRedirectUriThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .postLogoutRedirectUri("invalid uri") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo("invalid_client_metadata"); - assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); - }); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenPostLogoutRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .postLogoutRedirectUri("https://client.example.com/oidc-post-logout#fragment") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo("invalid_client_metadata"); - assertThat(error.getDescription()).contains(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); - }); - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenInvalidTokenEndpointAuthenticationMethodThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .redirectUri("https://client.example.com"); - // @formatter:on - - String invalidClientMetadataErrorCode = "invalid_client_metadata"; - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName()); - assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException(principal, builder.build(), - invalidClientMetadataErrorCode, OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - // @formatter:on - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm("none"); - assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException(principal, builder.build(), - invalidClientMetadataErrorCode, OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - // @formatter:on - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256.getName()); - assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException(principal, builder.build(), - invalidClientMetadataErrorCode, OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - // @formatter:on - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()) - .jwkSetUrl("https://client.example.com/jwks") - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName()); - assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException(principal, builder.build(), - invalidClientMetadataErrorCode, OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - // @formatter:on - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256.getName()); - assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException(principal, builder.build(), - invalidClientMetadataErrorCode, OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); - // @formatter:on - } - - private void assertWhenClientRegistrationRequestInvalidThenThrowOAuth2AuthenticationException( - Authentication principal, OidcClientRegistration clientRegistration, String errorCode, - String errorDescription) { - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).contains(errorDescription); - }); - } - - @Test - public void authenticateWhenTokenEndpointAuthenticationSigningAlgorithmNotProvidedThenDefaults() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - given(this.jwtEncoder.encode(any())).willReturn(createJwtClientConfiguration()); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .redirectUri("https://client.example.com") - .scope("scope1"); - // @formatter:on - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - // @formatter:on - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, builder.build()); - OidcClientRegistrationAuthenticationToken authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(MacAlgorithm.HS256.getName()); - assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNotNull(); - verify(this.passwordEncoder).encode(any()); - reset(this.passwordEncoder); - - // @formatter:off - builder - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()) - .jwkSetUrl("https://client.example.com/jwks"); - // @formatter:on - authentication = new OidcClientRegistrationAuthenticationToken(principal, builder.build()); - authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - assertThat(authenticationResult.getClientRegistration().getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(authenticationResult.getClientRegistration().getClientSecret()).isNull(); - verifyNoInteractions(this.passwordEncoder); - } - - @Test - public void authenticateWhenRegistrationAccessTokenNotGeneratedThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - doReturn(null).when(this.tokenGenerator).generate(any()); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - assertThat(error.getDescription()) - .contains("The token generator failed to generate the registration access token."); - }); - } - - @Test - public void authenticateWhenValidAccessTokenThenReturnClientRegistration() { - Jwt jwt = createJwtClientRegistration(); - OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, jwtAccessToken, jwt.getClaims()) - .build(); - given(this.authorizationService.findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - given(this.jwtEncoder.encode(any())).willReturn(createJwtClientConfiguration()); - - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .postLogoutRedirectUri("https://client.example.com/oidc-post-logout") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - principal, clientRegistration); - OidcClientRegistrationAuthenticationToken authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - ArgumentCaptor registeredClientCaptor = ArgumentCaptor.forClass(RegisteredClient.class); - ArgumentCaptor authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class); - - verify(this.authorizationService).findByToken(eq(jwtAccessToken.getTokenValue()), - eq(OAuth2TokenType.ACCESS_TOKEN)); - verify(this.registeredClientRepository).save(registeredClientCaptor.capture()); - verify(this.authorizationService, times(2)).save(authorizationCaptor.capture()); - verify(this.jwtEncoder).encode(any()); - verify(this.passwordEncoder).encode(any()); - - // assert "registration" access token, which should be used for subsequent calls - // to client configuration endpoint - OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0); - assertThat(authorizationResult.getAccessToken().getToken().getScopes()).containsExactly("client.read"); - assertThat(authorizationResult.getAccessToken().isActive()).isTrue(); - assertThat(authorizationResult.getRefreshToken()).isNull(); - - // assert "initial" access token is invalidated - authorizationResult = authorizationCaptor.getAllValues().get(1); - assertThat(authorizationResult.getAccessToken().isInvalidated()).isTrue(); - if (authorizationResult.getRefreshToken() != null) { - assertThat(authorizationResult.getRefreshToken().isInvalidated()).isTrue(); - } - - RegisteredClient registeredClientResult = registeredClientCaptor.getValue(); - assertThat(registeredClientResult.getId()).isNotNull(); - assertThat(registeredClientResult.getClientId()).isNotNull(); - assertThat(registeredClientResult.getClientIdIssuedAt()).isNotNull(); - assertThat(registeredClientResult.getClientSecret()).isNotNull(); - assertThat(registeredClientResult.getClientName()).isEqualTo(clientRegistration.getClientName()); - assertThat(registeredClientResult.getClientAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(registeredClientResult.getRedirectUris()).containsExactly("https://client.example.com"); - assertThat(registeredClientResult.getPostLogoutRedirectUris()) - .containsExactly("https://client.example.com/oidc-post-logout"); - assertThat(registeredClientResult.getAuthorizationGrantTypes()).containsExactlyInAnyOrder( - AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS); - assertThat(registeredClientResult.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); - assertThat(registeredClientResult.getClientSettings().isRequireProofKey()).isTrue(); - assertThat(registeredClientResult.getClientSettings().isRequireAuthorizationConsent()).isTrue(); - assertThat(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256); - - OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration(); - assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClientResult.getClientId()); - assertThat(clientRegistrationResult.getClientIdIssuedAt()) - .isEqualTo(registeredClientResult.getClientIdIssuedAt()); - assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClientResult.getClientSecret()); - assertThat(clientRegistrationResult.getClientSecretExpiresAt()) - .isEqualTo(registeredClientResult.getClientSecretExpiresAt()); - assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClientResult.getClientName()); - assertThat(clientRegistrationResult.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(registeredClientResult.getRedirectUris()); - assertThat(clientRegistrationResult.getPostLogoutRedirectUris()) - .containsExactlyInAnyOrderElementsOf(registeredClientResult.getPostLogoutRedirectUris()); - - List grantTypes = new ArrayList<>(); - registeredClientResult.getAuthorizationGrantTypes() - .forEach((authorizationGrantType) -> grantTypes.add(authorizationGrantType.getValue())); - assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes); - - assertThat(clientRegistrationResult.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientRegistrationResult.getScopes()) - .containsExactlyInAnyOrderElementsOf(registeredClientResult.getScopes()); - assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod()) - .isEqualTo(registeredClientResult.getClientAuthenticationMethods().iterator().next().getValue()); - assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - - AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); - String expectedRegistrationClientUrl = UriComponentsBuilder - .fromUriString(authorizationServerContext.getIssuer()) - .path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint()) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClientResult.getClientId()) - .toUriString(); - - assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()) - .isEqualTo(expectedRegistrationClientUrl); - assertThat(clientRegistrationResult.getRegistrationAccessToken()).isEqualTo(jwt.getTokenValue()); - } - - private static Jwt createJwtClientRegistration() { - return createJwt(Collections.singleton("client.create")); - } - - private static Jwt createJwtClientConfiguration() { - return createJwt(Collections.singleton("client.read")); - } - - private static Jwt createJwt(Set scopes) { - // @formatter:off - JwsHeader jwsHeader = TestJwsHeaders.jwsHeader() - .build(); - JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet() - .claim(OAuth2ParameterNames.SCOPE, scopes) - .build(); - Jwt jwt = Jwt.withTokenValue("jwt-access-token") - .headers((headers) -> headers.putAll(jwsHeader.getHeaders())) - .claims((claims) -> claims.putAll(jwtClaimsSet.getClaims())) - .build(); - // @formatter:on - return jwt; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java deleted file mode 100644 index 83734326c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.oidc.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OidcClientRegistrationAuthenticationToken}. - * - * @author Joe Grandja - */ -public class OidcClientRegistrationAuthenticationTokenTests { - - private TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - - private OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - - @Test - public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(null, this.clientRegistration)) - .withMessage("principal cannot be null"); - } - - @Test - public void constructorWhenClientRegistrationNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy( - () -> new OidcClientRegistrationAuthenticationToken(this.principal, (OidcClientRegistration) null)) - .withMessage("clientRegistration cannot be null"); - } - - @Test - public void constructorWhenClientIdNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (String) null)) - .withMessage("clientId cannot be empty"); - } - - @Test - public void constructorWhenClientIdEmptyThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, "")) - .withMessage("clientId cannot be empty"); - } - - @Test - public void constructorWhenOidcClientRegistrationProvidedThenCreated() { - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - this.principal, this.clientRegistration); - - assertThat(authentication.getPrincipal()).isEqualTo(this.principal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration); - assertThat(authentication.getClientId()).isNull(); - assertThat(authentication.isAuthenticated()).isEqualTo(this.principal.isAuthenticated()); - } - - @Test - public void constructorWhenClientIdProvidedThenCreated() { - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( - this.principal, "client-1"); - - assertThat(authentication.getPrincipal()).isEqualTo(this.principal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getClientRegistration()).isNull(); - assertThat(authentication.getClientId()).isEqualTo("client-1"); - assertThat(authentication.isAuthenticated()).isEqualTo(this.principal.isAuthenticated()); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java deleted file mode 100644 index 6b15c9c19..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProviderTests.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.function.Consumer; - -import org.junit.jupiter.api.AfterEach; -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.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OidcLogoutAuthenticationProvider}. - * - * @author Joe Grandja - */ -public class OidcLogoutAuthenticationProviderTests { - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private RegisteredClientRepository registeredClientRepository; - - private OAuth2AuthorizationService authorizationService; - - private SessionRegistry sessionRegistry; - - private AuthorizationServerSettings authorizationServerSettings; - - private OidcLogoutAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.sessionRegistry = mock(SessionRegistry.class); - this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - this.authorizationServerSettings, null); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - this.authenticationProvider = new OidcLogoutAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, this.sessionRegistry); - } - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy( - () -> new OidcLogoutAuthenticationProvider(null, this.authorizationService, this.sessionRegistry)) - .withMessage("registeredClientRepository cannot be null"); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy( - () -> new OidcLogoutAuthenticationProvider(this.registeredClientRepository, null, this.sessionRegistry)) - .withMessage("authorizationService cannot be null"); - } - - @Test - public void constructorWhenSessionRegistryNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationProvider(this.registeredClientRepository, - this.authorizationService, null)) - .withMessage("sessionRegistry cannot be null"); - } - - @Test - public void supportsWhenTypeOidcLogoutAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OidcLogoutAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenIdTokenNotFoundThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", principal, - "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("id_token_hint"); - }); - - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - } - - @Test - public void authenticateWhenIdTokenInvalidatedThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, (metadata) -> { - metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()); - metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - }) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("id_token_hint"); - }); - - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - } - - @Test - public void authenticateWhenMissingAudienceThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenInvalidAudienceThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId() + "-invalid")) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains(IdTokenClaimNames.AUD); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", registeredClient.getClientId() + "-invalid", null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenInvalidPostLogoutRedirectUriThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", registeredClient.getClientId(), "https://example.com/callback-1-invalid", null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - assertThat(error.getDescription()).contains("post_logout_redirect_uri"); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationValidator cannot be null"); - } - - @Test - public void authenticateWhenCustomAuthenticationValidatorThenUsed() throws NoSuchAlgorithmException { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String sessionId = "session-1"; - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .claim("sid", createHash(sessionId)) - .build(); - - @SuppressWarnings("unchecked") - Consumer authenticationValidator = mock(Consumer.class); - this.authenticationProvider.setAuthenticationValidator(authenticationValidator); - - authenticateValidIdToken(principal, registeredClient, sessionId, idToken); - verify(authenticationValidator).accept(any(OidcLogoutAuthenticationContext.class)); - } - - @Test - public void authenticateWhenMissingSubThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - principal.setAuthenticated(true); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("sub"); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - // gh-1235 - @Test - public void authenticateWhenInvalidPrincipalThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - principal.setAuthenticated(true); - - TestingAuthenticationToken otherPrincipal = new TestingAuthenticationToken("other-principal", "credentials"); - otherPrincipal.setAuthenticated(true); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - otherPrincipal, "session-1", null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("sub"); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenMissingSidThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - String sessionId = "session-1"; - List sessions = Collections - .singletonList(new SessionInformation(principal.getPrincipal(), sessionId, Date.from(Instant.now()))); - given(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true))).willReturn(sessions); - - principal.setAuthenticated(true); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, sessionId, null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("sid"); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenInvalidSidThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .claim("sid", "other-session") - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - String sessionId = "session-1"; - List sessions = Collections - .singletonList(new SessionInformation(principal.getPrincipal(), sessionId, Date.from(Instant.now()))); - given(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true))).willReturn(sessions); - - principal.setAuthenticated(true); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, sessionId, null, null, null); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - assertThat(error.getDescription()).contains("sid"); - }); - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - } - - @Test - public void authenticateWhenValidIdTokenThenAuthenticated() throws Exception { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String sessionId = "session-1"; - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().plusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .claim("sid", createHash(sessionId)) - .build(); - authenticateValidIdToken(principal, registeredClient, sessionId, idToken); - } - - // gh-1440 - @Test - public void authenticateWhenValidExpiredIdTokenThenAuthenticated() throws Exception { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String sessionId = "session-1"; - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject(principal.getName()) - .audience(Collections.singleton(registeredClient.getClientId())) - .issuedAt(Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS)) - .expiresAt(Instant.now().minusSeconds(30).truncatedTo(ChronoUnit.MILLIS)) // Expired - .claim("sid", createHash(sessionId)) - .build(); - authenticateValidIdToken(principal, registeredClient, sessionId, idToken); - } - - private void authenticateValidIdToken(Authentication principal, RegisteredClient registeredClient, String sessionId, - OidcIdToken idToken) { - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName(principal.getName()) - .token(idToken, - (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims())) - .build(); - given(this.authorizationService.findByToken(eq(idToken.getTokenValue()), eq(ID_TOKEN_TOKEN_TYPE))) - .willReturn(authorization); - given(this.registeredClientRepository.findById(eq(authorization.getRegisteredClientId()))) - .willReturn(registeredClient); - - SessionInformation sessionInformation = new SessionInformation(principal.getPrincipal(), sessionId, - Date.from(Instant.now())); - List sessions = Collections.singletonList(sessionInformation); - given(this.sessionRegistry.getAllSessions(eq(principal.getPrincipal()), eq(true))).willReturn(sessions); - - principal.setAuthenticated(true); - String postLogoutRedirectUri = registeredClient.getPostLogoutRedirectUris().toArray(new String[0])[0]; - String state = "state"; - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(idToken.getTokenValue(), - principal, sessionId, registeredClient.getClientId(), postLogoutRedirectUri, state); - - OidcLogoutAuthenticationToken authenticationResult = (OidcLogoutAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - verify(this.authorizationService).findByToken(eq(authentication.getIdTokenHint()), eq(ID_TOKEN_TOKEN_TYPE)); - verify(this.registeredClientRepository).findById(eq(authorization.getRegisteredClientId())); - - assertThat(authenticationResult.getPrincipal()).isEqualTo(principal); - assertThat(authenticationResult.getCredentials().toString()).isEmpty(); - assertThat(authenticationResult.getIdToken()).isEqualTo(idToken); - assertThat(authenticationResult.getSessionId()).isEqualTo(sessionInformation.getSessionId()); - assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId()); - assertThat(authenticationResult.getPostLogoutRedirectUri()).isEqualTo(postLogoutRedirectUri); - assertThat(authenticationResult.getState()).isEqualTo(state); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - } - - private static String createHash(String value) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value.getBytes(StandardCharsets.US_ASCII)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java deleted file mode 100644 index 9b4a874b9..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationTokenTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.authentication; - -import java.time.Instant; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OidcLogoutAuthenticationToken}. - * - * @author Joe Grandja - */ -public class OidcLogoutAuthenticationTokenTests { - - private final String idTokenHint = "id-token"; - - private final OidcIdToken idToken = OidcIdToken.withTokenValue(this.idTokenHint) - .issuer("https://provider.com") - .subject("principal") - .issuedAt(Instant.now().minusSeconds(60)) - .expiresAt(Instant.now().plusSeconds(60)) - .build(); - - private final TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - - private final String sessionId = "session-1"; - - private final String clientId = "client-1"; - - private final String postLogoutRedirectUri = "https://example.com/oidc-post-logout"; - - private final String state = "state-1"; - - @Test - public void constructorWhenIdTokenHintEmptyThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationToken("", this.principal, this.sessionId, this.clientId, - this.postLogoutRedirectUri, this.state)) - .withMessage("idTokenHint cannot be empty"); - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationToken((String) null, this.principal, this.sessionId, - this.clientId, this.postLogoutRedirectUri, this.state)) - .withMessage("idTokenHint cannot be empty"); - } - - @Test - public void constructorWhenIdTokenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationToken((OidcIdToken) null, this.principal, this.sessionId, - this.clientId, this.postLogoutRedirectUri, this.state)) - .withMessage("idToken cannot be null"); - } - - @Test - public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationToken(this.idTokenHint, null, this.sessionId, this.clientId, - this.postLogoutRedirectUri, this.state)) - .withMessage("principal cannot be null"); - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcLogoutAuthenticationToken(this.idToken, null, this.sessionId, this.clientId, - this.postLogoutRedirectUri, this.state)) - .withMessage("principal cannot be null"); - } - - @Test - public void constructorWhenIdTokenHintProvidedThenCreated() { - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(this.idTokenHint, - this.principal, this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state); - assertThat(authentication.getPrincipal()).isEqualTo(this.principal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getIdTokenHint()).isEqualTo(this.idTokenHint); - assertThat(authentication.getIdToken()).isNull(); - assertThat(authentication.getSessionId()).isEqualTo(this.sessionId); - assertThat(authentication.getClientId()).isEqualTo(this.clientId); - assertThat(authentication.getPostLogoutRedirectUri()).isEqualTo(this.postLogoutRedirectUri); - assertThat(authentication.getState()).isEqualTo(this.state); - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - public void constructorWhenIdTokenProvidedThenCreated() { - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken(this.idToken, this.principal, - this.sessionId, this.clientId, this.postLogoutRedirectUri, this.state); - assertThat(authentication.getPrincipal()).isEqualTo(this.principal); - assertThat(authentication.getCredentials().toString()).isEmpty(); - assertThat(authentication.getIdTokenHint()).isEqualTo(this.idToken.getTokenValue()); - assertThat(authentication.getIdToken()).isEqualTo(this.idToken); - assertThat(authentication.getSessionId()).isEqualTo(this.sessionId); - assertThat(authentication.getClientId()).isEqualTo(this.clientId); - assertThat(authentication.getPostLogoutRedirectUri()).isEqualTo(this.postLogoutRedirectUri); - assertThat(authentication.getState()).isEqualTo(this.state); - assertThat(authentication.isAuthenticated()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProviderTests.java deleted file mode 100644 index e6a4caaab..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProviderTests.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.authentication; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcUserInfoAuthenticationProvider}. - * - * @author Steve Riesenberg - */ -public class OidcUserInfoAuthenticationProviderTests { - - private OAuth2AuthorizationService authorizationService; - - private OidcUserInfoAuthenticationProvider authenticationProvider; - - @BeforeEach - public void setUp() { - this.authorizationService = mock(OAuth2AuthorizationService.class); - this.authenticationProvider = new OidcUserInfoAuthenticationProvider(this.authorizationService); - } - - @Test - public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> new OidcUserInfoAuthenticationProvider(null)) - .withMessage("authorizationService cannot be null"); - } - - @Test - public void setUserInfoMapperWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.authenticationProvider.setUserInfoMapper(null)) - .withMessage("userInfoMapper cannot be null"); - } - - @Test - public void supportsWhenTypeOidcUserInfoAuthenticationTokenThenReturnTrue() { - assertThat(this.authenticationProvider.supports(OidcUserInfoAuthenticationToken.class)).isTrue(); - } - - @Test - public void authenticateWhenPrincipalNotOfExpectedTypeThenThrowOAuth2AuthenticationException() { - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken( - new UsernamePasswordAuthenticationToken(null, null)); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - - verifyNoInteractions(this.authorizationService); - } - - @Test - public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - String tokenValue = "token"; - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - principal.setAuthenticated(false); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - - verifyNoInteractions(this.authorizationService); - } - - @Test - public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { - String tokenValue = "token"; - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - - verify(this.authorizationService).findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { - String tokenValue = "token"; - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build(); - authorization = OAuth2Authorization.from(authorization) - .invalidate(authorization.getAccessToken().getToken()) - .build(); - given(this.authorizationService.findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - - verify(this.authorizationService).findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() { - String tokenValue = "token"; - given(this.authorizationService.findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(TestOAuth2Authorizations.authorization().build()); - - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - - verify(this.authorizationService).findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenIdTokenNullThenThrowOAuth2AuthenticationException() { - String tokenValue = "token"; - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization() - .token(createAuthorization(tokenValue).getAccessToken().getToken()) - .build(); - given(this.authorizationService.findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(authorization); - - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - - verify(this.authorizationService).findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - @Test - public void authenticateWhenValidAccessTokenThenReturnUserInfo() { - String tokenValue = "access-token"; - given(this.authorizationService.findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN))) - .willReturn(createAuthorization(tokenValue)); - - JwtAuthenticationToken principal = createJwtAuthenticationToken(tokenValue); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - OidcUserInfoAuthenticationToken authenticationResult = (OidcUserInfoAuthenticationToken) this.authenticationProvider - .authenticate(authentication); - - assertThat(authenticationResult.getPrincipal()).isEqualTo(principal); - assertThat(authenticationResult.getCredentials()).isEqualTo(""); - assertThat(authenticationResult.isAuthenticated()).isTrue(); - - OidcUserInfo userInfo = authenticationResult.getUserInfo(); - assertThat(userInfo.getClaims()).hasSize(20); - assertThat(userInfo.getSubject()).isEqualTo("user1"); - assertThat(userInfo.getFullName()).isEqualTo("First Last"); - assertThat(userInfo.getGivenName()).isEqualTo("First"); - assertThat(userInfo.getFamilyName()).isEqualTo("Last"); - assertThat(userInfo.getMiddleName()).isEqualTo("Middle"); - assertThat(userInfo.getNickName()).isEqualTo("User"); - assertThat(userInfo.getPreferredUsername()).isEqualTo("user"); - assertThat(userInfo.getProfile()).isEqualTo("https://example.com/user1"); - assertThat(userInfo.getPicture()).isEqualTo("https://example.com/user1.jpg"); - assertThat(userInfo.getWebsite()).isEqualTo("https://example.com"); - assertThat(userInfo.getEmail()).isEqualTo("user1@example.com"); - assertThat(userInfo.getEmailVerified()).isEqualTo(true); - assertThat(userInfo.getGender()).isEqualTo("female"); - assertThat(userInfo.getBirthdate()).isEqualTo("1970-01-01"); - assertThat(userInfo.getZoneInfo()).isEqualTo("Europe/Paris"); - assertThat(userInfo.getLocale()).isEqualTo("en-US"); - assertThat(userInfo.getPhoneNumber()).isEqualTo("+1 (604) 555-1234;ext=5678"); - assertThat(userInfo.getPhoneNumberVerified()).isEqualTo(false); - assertThat(userInfo.getAddress().getFormatted()) - .isEqualTo("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"); - assertThat(userInfo.getUpdatedAt()).isEqualTo(Instant.parse("1970-01-01T00:00:00Z")); - - verify(this.authorizationService).findByToken(eq(tokenValue), eq(OAuth2TokenType.ACCESS_TOKEN)); - } - - private static OAuth2Authorization createAuthorization(String tokenValue) { - Instant now = Instant.now(); - Set scopes = new HashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, - OidcScopes.PHONE, OidcScopes.PROFILE)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, now, - now.plusSeconds(300), scopes); - OidcIdToken idToken = new OidcIdToken("id-token", now, now.plusSeconds(900), createUserInfo().getClaims()); - - return TestOAuth2Authorizations.authorization().token(accessToken).token(idToken).build(); - } - - private static JwtAuthenticationToken createJwtAuthenticationToken(String tokenValue) { - Instant now = Instant.now(); - // @formatter:off - Jwt jwt = Jwt.withTokenValue(tokenValue) - .header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName()) - .issuedAt(now) - .expiresAt(now.plusSeconds(300)) - .claim(StandardClaimNames.SUB, "user") - .build(); - // @formatter:on - return new JwtAuthenticationToken(jwt, Collections.emptyList()); - } - - private static OidcUserInfo createUserInfo() { - // @formatter:off - return OidcUserInfo.builder() - .subject("user1") - .name("First Last") - .givenName("First") - .familyName("Last") - .middleName("Middle") - .nickname("User") - .preferredUsername("user") - .profile("https://example.com/user1") - .picture("https://example.com/user1.jpg") - .website("https://example.com") - .email("user1@example.com") - .emailVerified(true) - .gender("female") - .birthdate("1970-01-01") - .zoneinfo("Europe/Paris") - .locale("en-US") - .phoneNumber("+1 (604) 555-1234;ext=5678") - .phoneNumberVerified(false) - .claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) - .updatedAt("1970-01-01T00:00:00Z") - .build(); - // @formatter:on - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationTokenTests.java deleted file mode 100644 index 49756b3ed..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationTokenTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.oidc.authentication; - -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link OidcUserInfoAuthenticationToken}. - * - * @author Steve Riesenberg - */ -public class OidcUserInfoAuthenticationTokenTests { - - @Test - public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> new OidcUserInfoAuthenticationToken(null)) - .withMessage("principal cannot be null"); - } - - @Test - public void constructorWhenPrincipalProvidedThenCreated() { - UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(null, null); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - assertThat(authentication.getPrincipal()).isEqualTo(principal); - assertThat(authentication.getUserInfo()).isNull(); - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - public void constructorWhenPrincipalAndUserInfoProvidedThenCreated() { - UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(null, null); - OidcUserInfo userInfo = new OidcUserInfo(Collections.singletonMap(StandardClaimNames.SUB, "user")); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal, userInfo); - assertThat(authentication.getPrincipal()).isEqualTo(principal); - assertThat(authentication.getUserInfo()).isEqualTo(userInfo); - assertThat(authentication.isAuthenticated()).isTrue(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java deleted file mode 100644 index 60d70e1d2..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.http.converter; - -import java.net.URL; -import java.time.Instant; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; - -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.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OidcClientRegistrationHttpMessageConverter} - * - * @author Ovidiu Popa - * @author Joe Grandja - * @since 0.1.1 - */ -public class OidcClientRegistrationHttpMessageConverterTests { - - private final OidcClientRegistrationHttpMessageConverter messageConverter = new OidcClientRegistrationHttpMessageConverter(); - - @Test - public void supportsWhenOidcClientRegistrationThenTrue() { - assertThat(this.messageConverter.supports(OidcClientRegistration.class)).isTrue(); - } - - @Test - public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setClientRegistrationConverter(null)) - .withMessageContaining("clientRegistrationConverter cannot be null"); - } - - @Test - public void setClientRegistrationParametersConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setClientRegistrationParametersConverter(null)) - .withMessageContaining("clientRegistrationParametersConverter cannot be null"); - } - - @Test - public void readInternalWhenRequiredParametersThenSuccess() { - // @formatter:off - String clientRegistrationRequest = "{\n" - + " \"redirect_uris\": [\n" - + " \"https://client.example.com\"\n" - + " ]\n" - + "}\n"; - // @formatter:on - - MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationRequest.getBytes(), - HttpStatus.OK); - OidcClientRegistration clientRegistration = this.messageConverter.readInternal(OidcClientRegistration.class, - response); - - assertThat(clientRegistration.getClaims()).hasSize(1); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - } - - @Test - public void readInternalWhenValidParametersThenSuccess() throws Exception { - // @formatter:off - String clientRegistrationRequest = "{\n" - + " \"client_id\": \"client-id\",\n" - + " \"client_id_issued_at\": 1607633867,\n" - + " \"client_secret\": \"client-secret\",\n" - + " \"client_secret_expires_at\": 1607637467,\n" - + " \"client_name\": \"client-name\",\n" - + " \"redirect_uris\": [\n" - + " \"https://client.example.com\"\n" - + " ],\n" - + " \"post_logout_redirect_uris\": [\n" - + " \"https://client.example.com/oidc-post-logout\"\n" - + " ],\n" - + " \"token_endpoint_auth_method\": \"client_secret_jwt\",\n" - + " \"token_endpoint_auth_signing_alg\": \"HS256\",\n" - + " \"grant_types\": [\n" - + " \"authorization_code\",\n" - + " \"client_credentials\"\n" - + " ],\n" - + " \"response_types\":[\n" - + " \"code\"\n" - + " ],\n" - + " \"scope\": \"scope1 scope2\",\n" - + " \"jwks_uri\": \"https://client.example.com/jwks\",\n" - + " \"id_token_signed_response_alg\": \"RS256\",\n" - + " \"a-claim\": \"a-value\"\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationRequest.getBytes(), - HttpStatus.OK); - OidcClientRegistration clientRegistration = this.messageConverter.readInternal(OidcClientRegistration.class, - response); - - assertThat(clientRegistration.getClientId()).isEqualTo("client-id"); - assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(Instant.ofEpochSecond(1607633867L)); - assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret"); - assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(Instant.ofEpochSecond(1607637467L)); - assertThat(clientRegistration.getClientName()).isEqualTo("client-name"); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - assertThat(clientRegistration.getPostLogoutRedirectUris()) - .containsOnly("https://client.example.com/oidc-post-logout"); - assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()); - assertThat(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()) - .isEqualTo(MacAlgorithm.HS256.getName()); - assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(clientRegistration.getResponseTypes()).containsOnly("code"); - assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); - assertThat(clientRegistration.getJwkSetUrl()).isEqualTo(new URL("https://client.example.com/jwks")); - assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256"); - assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); - } - - @Test - public void readInternalWhenClientSecretNoExpiryThenSuccess() { - // @formatter:off - String clientRegistrationRequest = "{\n" - + " \"client_id\": \"client-id\",\n" - + " \"client_secret\": \"client-secret\",\n" - + " \"client_secret_expires_at\": 0,\n" - + " \"redirect_uris\": [\n" - + " \"https://client.example.com\"\n" - + " ]\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationRequest.getBytes(), - HttpStatus.OK); - OidcClientRegistration clientRegistration = this.messageConverter.readInternal(OidcClientRegistration.class, - response); - - assertThat(clientRegistration.getClaims()).hasSize(3); - assertThat(clientRegistration.getClientId()).isEqualTo("client-id"); - assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret"); - assertThat(clientRegistration.getClientSecretExpiresAt()).isNull(); - assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com"); - } - - @Test - public void readInternalWhenFailingConverterThenThrowException() { - String errorMessage = "this is not a valid converter"; - this.messageConverter.setClientRegistrationConverter((source) -> { - throw new RuntimeException(errorMessage); - }); - MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OidcClientRegistration.class, response)) - .withMessageContaining("An error occurred reading the OpenID Client Registration") - .withMessageContaining(errorMessage); - } - - @Test - public void writeInternalWhenClientRegistrationThenSuccess() { - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientId("client-id") - .clientIdIssuedAt(Instant.ofEpochSecond(1607633867)) - .clientSecret("client-secret") - .clientSecretExpiresAt(Instant.ofEpochSecond(1607637467)) - .clientName("client-name") - .redirectUri("https://client.example.com") - .postLogoutRedirectUri("https://client.example.com/oidc-post-logout") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256.getName()) - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) - .scope("scope1") - .scope("scope2") - .jwkSetUrl("https://client.example.com/jwks") - .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .registrationAccessToken("registration-access-token") - .registrationClientUrl("https://auth-server.com/connect/register?client_id=1") - .claim("a-claim", "a-value") - .build(); - // @formatter:on - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - this.messageConverter.writeInternal(clientRegistration, outputMessage); - - String clientRegistrationResponse = outputMessage.getBodyAsString(); - assertThat(clientRegistrationResponse).contains("\"client_id\":\"client-id\""); - assertThat(clientRegistrationResponse).contains("\"client_id_issued_at\":1607633867"); - assertThat(clientRegistrationResponse).contains("\"client_secret\":\"client-secret\""); - assertThat(clientRegistrationResponse).contains("\"client_secret_expires_at\":1607637467"); - assertThat(clientRegistrationResponse).contains("\"client_name\":\"client-name\""); - assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"https://client.example.com\"]"); - assertThat(clientRegistrationResponse) - .contains("\"post_logout_redirect_uris\":[\"https://client.example.com/oidc-post-logout\"]"); - assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_method\":\"client_secret_jwt\""); - assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_signing_alg\":\"HS256\""); - assertThat(clientRegistrationResponse) - .contains("\"grant_types\":[\"authorization_code\",\"client_credentials\"]"); - assertThat(clientRegistrationResponse).contains("\"response_types\":[\"code\"]"); - assertThat(clientRegistrationResponse).contains("\"scope\":\"scope1 scope2\""); - assertThat(clientRegistrationResponse).contains("\"jwks_uri\":\"https://client.example.com/jwks\""); - assertThat(clientRegistrationResponse).contains("\"id_token_signed_response_alg\":\"RS256\""); - assertThat(clientRegistrationResponse).contains("\"registration_access_token\":\"registration-access-token\""); - assertThat(clientRegistrationResponse) - .contains("\"registration_client_uri\":\"https://auth-server.com/connect/register?client_id=1\""); - assertThat(clientRegistrationResponse).contains("\"a-claim\":\"a-value\""); - } - - @Test - public void writeInternalWhenClientSecretNoExpiryThenSuccess() { - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientId("client-id") - .clientSecret("client-secret") - .redirectUri("https://client.example.com") - .build(); - // @formatter:on - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - this.messageConverter.writeInternal(clientRegistration, outputMessage); - - String clientRegistrationResponse = outputMessage.getBodyAsString(); - assertThat(clientRegistrationResponse).contains("\"client_id\":\"client-id\""); - assertThat(clientRegistrationResponse).contains("\"client_secret\":\"client-secret\""); - assertThat(clientRegistrationResponse).contains("\"client_secret_expires_at\":0"); - assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"https://client.example.com\"]"); - } - - @Test - public void writeInternalWhenWriteFailsThenThrowException() { - String errorMessage = "this is not a valid converter"; - Converter> failingConverter = (source) -> { - throw new RuntimeException(errorMessage); - }; - this.messageConverter.setClientRegistrationParametersConverter(failingConverter); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .redirectUri("https://client.example.com") - .build(); - // @formatter:off - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - assertThatThrownBy(() -> this.messageConverter.writeInternal(clientRegistration, outputMessage)) - .isInstanceOf(HttpMessageNotWritableException.class) - .hasMessageContaining("An error occurred writing the OpenID Client Registration") - .hasMessageContaining(errorMessage); - } -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java deleted file mode 100644 index f0eef0ca1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverterTests.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.http.converter; - -import java.net.URL; -import java.util.Arrays; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; - -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 OidcProviderConfigurationHttpMessageConverter} - * - * @author Daniel Garnier-Moiroux - */ -public class OidcProviderConfigurationHttpMessageConverterTests { - - private final OidcProviderConfigurationHttpMessageConverter messageConverter = new OidcProviderConfigurationHttpMessageConverter(); - - @Test - public void supportsWhenOidcProviderConfigurationThenTrue() { - assertThat(this.messageConverter.supports(OidcProviderConfiguration.class)).isTrue(); - } - - @Test - public void setProviderConfigurationParametersConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setProviderConfigurationParametersConverter(null)); - } - - @Test - public void setProviderConfigurationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setProviderConfigurationConverter(null)); - } - - @Test - public void readInternalWhenRequiredParametersThenSuccess() throws Exception { - // @formatter:off - String providerConfigurationResponse = "{\n" - + " \"issuer\": \"https://example.com\",\n" - + " \"authorization_endpoint\": \"https://example.com/oauth2/authorize\",\n" - + " \"token_endpoint\": \"https://example.com/oauth2/token\",\n" - + " \"jwks_uri\": \"https://example.com/oauth2/jwks\",\n" - + " \"response_types_supported\": [\"code\"],\n" - + " \"subject_types_supported\": [\"public\"],\n" - + " \"id_token_signing_alg_values_supported\": [\"RS256\"]\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), - HttpStatus.OK); - OidcProviderConfiguration providerConfiguration = this.messageConverter - .readInternal(OidcProviderConfiguration.class, response); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(new URL("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(new URL("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getScopes()).isNull(); - assertThat(providerConfiguration.getGrantTypes()).isNull(); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull(); - } - - @Test - public void readInternalWhenValidParametersThenSuccess() throws Exception { - // @formatter:off - String providerConfigurationResponse = "{\n" - + " \"issuer\": \"https://example.com\",\n" - + " \"authorization_endpoint\": \"https://example.com/oauth2/authorize\",\n" - + " \"token_endpoint\": \"https://example.com/oauth2/token\",\n" - + " \"jwks_uri\": \"https://example.com/oauth2/jwks\",\n" - + " \"userinfo_endpoint\": \"https://example.com/userinfo\",\n" - + " \"scopes_supported\": [\"openid\"],\n" - + " \"response_types_supported\": [\"code\"],\n" - + " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n" - + " \"subject_types_supported\": [\"public\"],\n" - + " \"id_token_signing_alg_values_supported\": [\"RS256\"],\n" - + " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n" - + " \"custom_claim\": \"value\",\n" - + " \"custom_collection_claim\": [\"value1\", \"value2\"]\n" - + "}\n"; - // @formatter:on - MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), - HttpStatus.OK); - OidcProviderConfiguration providerConfiguration = this.messageConverter - .readInternal(OidcProviderConfiguration.class, response); - - assertThat(providerConfiguration.getIssuer()).isEqualTo(new URL("https://example.com")); - assertThat(providerConfiguration.getAuthorizationEndpoint()) - .isEqualTo(new URL("https://example.com/oauth2/authorize")); - assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/oauth2/token")); - assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(new URL("https://example.com/oauth2/jwks")); - assertThat(providerConfiguration.getUserInfoEndpoint()).isEqualTo(new URL("https://example.com/userinfo")); - assertThat(providerConfiguration.getScopes()).containsExactly("openid"); - assertThat(providerConfiguration.getResponseTypes()).containsExactly("code"); - assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", - "client_credentials"); - assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public"); - assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256"); - assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()) - .containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(providerConfiguration.getClaim("custom_claim")).isEqualTo("value"); - assertThat(providerConfiguration.getClaimAsStringList("custom_collection_claim")) - .containsExactlyInAnyOrder("value1", "value2"); - } - - @Test - public void readInternalWhenFailingConverterThenThrowException() { - String errorMessage = "this is not a valid converter"; - this.messageConverter.setProviderConfigurationConverter((source) -> { - throw new RuntimeException(errorMessage); - }); - MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OidcProviderConfiguration.class, response)) - .withMessageContaining("An error occurred reading the OpenID Provider Configuration") - .withMessageContaining(errorMessage); - } - - @Test - public void readInternalWhenInvalidProviderConfigurationThenThrowException() { - String providerConfigurationResponse = "{ \"issuer\": null }"; - MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), - HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OidcProviderConfiguration.class, response)) - .withMessageContaining("An error occurred reading the OpenID Provider Configuration") - .withMessageContaining("issuer cannot be null"); - } - - @Test - public void writeInternalWhenProviderConfigurationThenSuccess() { - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .jwkSetUrl("https://example.com/oauth2/jwks") - .userInfoEndpoint("https://example.com/userinfo") - .scope("openid") - .responseType("code") - .grantType("authorization_code") - .grantType("client_credentials") - .subjectType("public") - .idTokenSigningAlgorithm("RS256") - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .claim("custom_claim", "value") - .claim("custom_collection_claim", Arrays.asList("value1", "value2")) - .build(); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - this.messageConverter.writeInternal(providerConfiguration, outputMessage); - - String providerConfigurationResponse = outputMessage.getBodyAsString(); - assertThat(providerConfigurationResponse).contains("\"issuer\":\"https://example.com\""); - assertThat(providerConfigurationResponse) - .contains("\"authorization_endpoint\":\"https://example.com/oauth2/authorize\""); - assertThat(providerConfigurationResponse).contains("\"token_endpoint\":\"https://example.com/oauth2/token\""); - assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/jwks\""); - assertThat(providerConfigurationResponse).contains("\"userinfo_endpoint\":\"https://example.com/userinfo\""); - assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]"); - assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]"); - assertThat(providerConfigurationResponse) - .contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]"); - assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]"); - assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]"); - assertThat(providerConfigurationResponse) - .contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]"); - assertThat(providerConfigurationResponse).contains("\"custom_claim\":\"value\""); - assertThat(providerConfigurationResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]"); - } - - @Test - public void writeInternalWhenWriteFailsThenThrowsException() { - String errorMessage = "this is not a valid converter"; - Converter> failingConverter = (source) -> { - throw new RuntimeException(errorMessage); - }; - this.messageConverter.setProviderConfigurationParametersConverter(failingConverter); - - OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder() - .issuer("https://example.com") - .authorizationEndpoint("https://example.com/oauth2/authorize") - .tokenEndpoint("https://example.com/oauth2/token") - .jwkSetUrl("https://example.com/oauth2/jwks") - .responseType("code") - .subjectType("public") - .idTokenSigningAlgorithm("RS256") - .build(); - - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - assertThatExceptionOfType(HttpMessageNotWritableException.class) - .isThrownBy(() -> this.messageConverter.writeInternal(providerConfiguration, outputMessage)) - .withMessageContaining("An error occurred writing the OpenID Provider Configuration") - .withMessageContaining(errorMessage); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverterTests.java deleted file mode 100644 index a469dbc99..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverterTests.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.oidc.http.converter; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; - -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 OidcUserInfoHttpMessageConverter}. - * - * @author Steve Riesenberg - */ -public class OidcUserInfoHttpMessageConverterTests { - - private final OidcUserInfoHttpMessageConverter messageConverter = new OidcUserInfoHttpMessageConverter(); - - @Test - public void supportsWhenOidcUserInfoThenTrue() { - assertThat(this.messageConverter.supports(OidcUserInfo.class)).isTrue(); - } - - @Test - public void setUserInfoConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setUserInfoConverter(null)); - } - - @Test - public void setUserInfoParametersConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.messageConverter.setUserInfoParametersConverter(null)); - } - - @Test - public void readInternalWhenValidParametersThenSuccess() { - // @formatter:off - String userInfoResponse = "{\n" + - " \"sub\": \"user1\",\n" + - " \"name\": \"First Last\",\n" + - " \"given_name\": \"First\",\n" + - " \"family_name\": \"Last\",\n" + - " \"middle_name\": \"Middle\",\n" + - " \"nickname\": \"User\",\n" + - " \"preferred_username\": \"user\",\n" + - " \"profile\": \"https://example.com/user1\",\n" + - " \"picture\": \"https://example.com/user1.jpg\",\n" + - " \"website\": \"https://example.com\",\n" + - " \"email\": \"user1@example.com\",\n" + - " \"email_verified\": \"true\",\n" + - " \"gender\": \"female\",\n" + - " \"birthdate\": \"1970-01-01\",\n" + - " \"zoneinfo\": \"Europe/Paris\",\n" + - " \"locale\": \"en-US\",\n" + - " \"phone_number\": \"+1 (604) 555-1234;ext=5678\",\n" + - " \"phone_number_verified\": \"false\",\n" + - " \"address\": {\n" + - " \"formatted\": \"Champ de Mars\\n5 Av. Anatole France\\n75007 Paris\\nFrance\",\n" + - " \"street_address\": \"Champ de Mars\\n5 Av. Anatole France\",\n" + - " \"locality\": \"Paris\",\n" + - " \"postal_code\": \"75007\",\n" + - " \"country\": \"France\"\n" + - " },\n" + - " \"updated_at\": 1607633867\n" + - "}\n"; - // @formatter:on - - MockClientHttpResponse response = new MockClientHttpResponse(userInfoResponse.getBytes(), HttpStatus.OK); - OidcUserInfo oidcUserInfo = this.messageConverter.readInternal(OidcUserInfo.class, response); - - assertThat(oidcUserInfo.getSubject()).isEqualTo("user1"); - assertThat(oidcUserInfo.getFullName()).isEqualTo("First Last"); - assertThat(oidcUserInfo.getGivenName()).isEqualTo("First"); - assertThat(oidcUserInfo.getFamilyName()).isEqualTo("Last"); - assertThat(oidcUserInfo.getMiddleName()).isEqualTo("Middle"); - assertThat(oidcUserInfo.getNickName()).isEqualTo("User"); - assertThat(oidcUserInfo.getPreferredUsername()).isEqualTo("user"); - assertThat(oidcUserInfo.getProfile()).isEqualTo("https://example.com/user1"); - assertThat(oidcUserInfo.getPicture()).isEqualTo("https://example.com/user1.jpg"); - assertThat(oidcUserInfo.getWebsite()).isEqualTo("https://example.com"); - assertThat(oidcUserInfo.getEmail()).isEqualTo("user1@example.com"); - assertThat(oidcUserInfo.getEmailVerified()).isTrue(); - assertThat(oidcUserInfo.getGender()).isEqualTo("female"); - assertThat(oidcUserInfo.getBirthdate()).isEqualTo("1970-01-01"); - assertThat(oidcUserInfo.getZoneInfo()).isEqualTo("Europe/Paris"); - assertThat(oidcUserInfo.getLocale()).isEqualTo("en-US"); - assertThat(oidcUserInfo.getPhoneNumber()).isEqualTo("+1 (604) 555-1234;ext=5678"); - assertThat(oidcUserInfo.getPhoneNumberVerified()).isFalse(); - assertThat(oidcUserInfo.getAddress().getFormatted()) - .isEqualTo("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"); - assertThat(oidcUserInfo.getAddress().getStreetAddress()).isEqualTo("Champ de Mars\n5 Av. Anatole France"); - assertThat(oidcUserInfo.getAddress().getLocality()).isEqualTo("Paris"); - assertThat(oidcUserInfo.getAddress().getPostalCode()).isEqualTo("75007"); - assertThat(oidcUserInfo.getAddress().getCountry()).isEqualTo("France"); - assertThat(oidcUserInfo.getUpdatedAt()).isEqualTo(Instant.ofEpochSecond(1607633867)); - } - - @Test - public void readInternalWhenFailingConverterThenThrowException() { - String errorMessage = "this is not a valid converter"; - this.messageConverter.setUserInfoConverter((source) -> { - throw new RuntimeException(errorMessage); - }); - MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OidcUserInfo.class, response)) - .withMessageContaining("An error occurred reading the UserInfo response") - .withMessageContaining(errorMessage); - } - - @Test - public void readInternalWhenInvalidResponseThenThrowException() { - String userInfoResponse = "{}"; - MockClientHttpResponse response = new MockClientHttpResponse(userInfoResponse.getBytes(), HttpStatus.OK); - - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> this.messageConverter.readInternal(OidcUserInfo.class, response)) - .withMessageContaining("An error occurred reading the UserInfo response") - .withMessageContaining("claims cannot be empty"); - } - - @Test - public void writeInternalWhenOidcUserInfoThenSuccess() { - OidcUserInfo userInfo = createUserInfo(); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - this.messageConverter.writeInternal(userInfo, outputMessage); - - String userInfoResponse = outputMessage.getBodyAsString(); - assertThat(userInfoResponse).contains("\"sub\":\"user1\""); - assertThat(userInfoResponse).contains("\"name\":\"First Last\""); - assertThat(userInfoResponse).contains("\"given_name\":\"First\""); - assertThat(userInfoResponse).contains("\"family_name\":\"Last\""); - assertThat(userInfoResponse).contains("\"middle_name\":\"Middle\""); - assertThat(userInfoResponse).contains("\"nickname\":\"User\""); - assertThat(userInfoResponse).contains("\"preferred_username\":\"user\""); - assertThat(userInfoResponse).contains("\"profile\":\"https://example.com/user1\""); - assertThat(userInfoResponse).contains("\"picture\":\"https://example.com/user1.jpg\""); - assertThat(userInfoResponse).contains("\"website\":\"https://example.com\""); - assertThat(userInfoResponse).contains("\"email\":\"user1@example.com\""); - assertThat(userInfoResponse).contains("\"email_verified\":true"); - assertThat(userInfoResponse).contains("\"gender\":\"female\""); - assertThat(userInfoResponse).contains("\"birthdate\":\"1970-01-01\""); - assertThat(userInfoResponse).contains("\"zoneinfo\":\"Europe/Paris\""); - assertThat(userInfoResponse).contains("\"locale\":\"en-US\""); - assertThat(userInfoResponse).contains("\"phone_number\":\"+1 (604) 555-1234;ext=5678\""); - assertThat(userInfoResponse).contains("\"phone_number_verified\":false"); - assertThat(userInfoResponse).contains("\"address\":"); - assertThat(userInfoResponse) - .contains("\"formatted\":\"Champ de Mars\\n5 Av. Anatole France\\n75007 Paris\\nFrance\""); - assertThat(userInfoResponse).contains("\"updated_at\":1607633867"); - assertThat(userInfoResponse).contains("\"custom_claim\":\"value\""); - assertThat(userInfoResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]"); - } - - @Test - public void writeInternalWhenWriteFailsThenThrowsException() { - String errorMessage = "this is not a valid converter"; - Converter> failingConverter = (source) -> { - throw new RuntimeException(errorMessage); - }; - this.messageConverter.setUserInfoParametersConverter(failingConverter); - - OidcUserInfo userInfo = createUserInfo(); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - - assertThatExceptionOfType(HttpMessageNotWritableException.class) - .isThrownBy(() -> this.messageConverter.writeInternal(userInfo, outputMessage)) - .withMessageContaining("An error occurred writing the UserInfo response") - .withMessageContaining(errorMessage); - } - - private static OidcUserInfo createUserInfo() { - return OidcUserInfo.builder() - .subject("user1") - .name("First Last") - .givenName("First") - .familyName("Last") - .middleName("Middle") - .nickname("User") - .preferredUsername("user") - .profile("https://example.com/user1") - .picture("https://example.com/user1.jpg") - .website("https://example.com") - .email("user1@example.com") - .emailVerified(true) - .gender("female") - .birthdate("1970-01-01") - .zoneinfo("Europe/Paris") - .locale("en-US") - .phoneNumber("+1 (604) 555-1234;ext=5678") - .claim("phone_number_verified", false) - .claim("address", - Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) - .claim(StandardClaimNames.UPDATED_AT, Instant.ofEpochSecond(1607633867)) - .claim("custom_claim", "value") - .claim("custom_collection_claim", Arrays.asList("value1", "value2")) - .build(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java deleted file mode 100644 index 5b57705f6..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java +++ /dev/null @@ -1,602 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.web; - -import java.io.IOException; -import java.time.Instant; -import java.util.Collections; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpRequest; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.TestJwsHeaders; -import org.springframework.security.oauth2.jwt.TestJwtClaimsSets; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.web.util.UriComponentsBuilder; - -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.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcClientRegistrationEndpointFilter}. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class OidcClientRegistrationEndpointFilterTests { - - private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register"; - - private AuthenticationManager authenticationManager; - - private OidcClientRegistrationEndpointFilter filter; - - private final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setup() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OidcClientRegistrationEndpointFilter(this.authenticationManager); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> new OidcClientRegistrationEndpointFilter(null)) - .withMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenClientRegistrationEndpointUriNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationEndpointFilter(this.authenticationManager, null)) - .withMessage("clientRegistrationEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .withMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .withMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .withMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotClientRegistrationRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenClientRegistrationRequestGetThenNotProcessed() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenClientRegistrationRequestInvalidThenInvalidRequestError() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setContent("invalid content".getBytes()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - assertThat(error.getDescription()).startsWith("OpenID Client Registration Error: "); - } - - @Test - public void doFilterWhenClientRegistrationRequestInvalidTokenThenUnauthorizedError() throws Exception { - doFilterWhenClientRegistrationRequestInvalidThenError(OAuth2ErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED); - } - - @Test - public void doFilterWhenClientRegistrationRequestInsufficientTokenScopeThenForbiddenError() throws Exception { - doFilterWhenClientRegistrationRequestInvalidThenError(OAuth2ErrorCodes.INSUFFICIENT_SCOPE, - HttpStatus.FORBIDDEN); - } - - private void doFilterWhenClientRegistrationRequestInvalidThenError(String errorCode, HttpStatus status) - throws Exception { - Jwt jwt = createJwt("client.create"); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - given(this.authenticationManager.authenticate(any())).willThrow(new OAuth2AuthenticationException(errorCode)); - - // @formatter:off - OidcClientRegistration clientRegistrationRequest = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - writeClientRegistrationRequest(request, clientRegistrationRequest); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(status.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - } - - @Test - public void doFilterWhenClientRegistrationRequestValidThenSuccessResponse() throws Exception { - // @formatter:off - OidcClientRegistration expectedClientRegistrationResponse = createClientRegistration(); - - OidcClientRegistration clientRegistrationRequest = OidcClientRegistration.builder() - .clientName(expectedClientRegistrationResponse.getClientName()) - .redirectUris((redirectUris) -> redirectUris.addAll(expectedClientRegistrationResponse.getRedirectUris())) - .grantTypes((grantTypes) -> grantTypes.addAll(expectedClientRegistrationResponse.getGrantTypes())) - .scopes((scopes) -> scopes.addAll(expectedClientRegistrationResponse.getScopes())) - .build(); - // @formatter:on - - Jwt jwt = createJwt("client.create"); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.create")); - - OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult = new OidcClientRegistrationAuthenticationToken( - principal, expectedClientRegistrationResponse); - - given(this.authenticationManager.authenticate(any())).willReturn(clientRegistrationAuthenticationResult); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - writeClientRegistrationRequest(request, clientRegistrationRequest); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value()); - OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(response); - assertThat(clientRegistrationResponse.getClientId()) - .isEqualTo(expectedClientRegistrationResponse.getClientId()); - assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isBetween( - expectedClientRegistrationResponse.getClientIdIssuedAt().minusSeconds(1), - expectedClientRegistrationResponse.getClientIdIssuedAt().plusSeconds(1)); - assertThat(clientRegistrationResponse.getClientSecret()) - .isEqualTo(expectedClientRegistrationResponse.getClientSecret()); - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()) - .isEqualTo(expectedClientRegistrationResponse.getClientSecretExpiresAt()); - assertThat(clientRegistrationResponse.getClientName()) - .isEqualTo(expectedClientRegistrationResponse.getClientName()); - assertThat(clientRegistrationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getRedirectUris()); - assertThat(clientRegistrationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getGrantTypes()); - assertThat(clientRegistrationResponse.getResponseTypes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getResponseTypes()); - assertThat(clientRegistrationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getScopes()); - assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod()); - assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientRegistrationResponse.getRegistrationAccessToken()) - .isEqualTo(expectedClientRegistrationResponse.getRegistrationAccessToken()); - assertThat(clientRegistrationResponse.getRegistrationClientUrl()) - .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUrl()); - } - - @Test - public void doFilterWhenClientConfigurationRequestPutThenNotProcessed() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("PUT", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenClientConfigurationRequestMissingClientIdThenNotProcessed() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenClientConfigurationRequestEmptyClientIdThenNotProcessed() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, ""); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenClientConfigurationRequestMultipleClientIdThenInvalidRequestError() throws Exception { - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-id"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-id2"); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void doFilterWhenClientConfigurationRequestInvalidTokenThenUnauthorizedError() throws Exception { - doFilterWhenClientConfigurationRequestInvalidThenError(OAuth2ErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED); - } - - @Test - public void doFilterWhenClientConfigurationRequestInsufficientScopeThenForbiddenError() throws Exception { - doFilterWhenClientConfigurationRequestInvalidThenError(OAuth2ErrorCodes.INSUFFICIENT_SCOPE, - HttpStatus.FORBIDDEN); - } - - @Test - public void doFilterWhenClientConfigurationRequestInvalidClientThenUnauthorizedError() throws Exception { - doFilterWhenClientConfigurationRequestInvalidThenError(OAuth2ErrorCodes.INVALID_CLIENT, - HttpStatus.UNAUTHORIZED); - } - - private void doFilterWhenClientConfigurationRequestInvalidThenError(String errorCode, HttpStatus status) - throws Exception { - Jwt jwt = createJwt("client.read"); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - given(this.authenticationManager.authenticate(any())).willThrow(new OAuth2AuthenticationException(errorCode)); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1"); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(status.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - } - - @Test - public void doFilterWhenClientConfigurationRequestValidThenSuccessResponse() throws Exception { - OidcClientRegistration expectedClientRegistrationResponse = createClientRegistration(); - - Jwt jwt = createJwt("client.read"); - JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, - AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken clientConfigurationAuthenticationResult = new OidcClientRegistrationAuthenticationToken( - principal, expectedClientRegistrationResponse); - - given(this.authenticationManager.authenticate(any())).willReturn(clientConfigurationAuthenticationResult); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, expectedClientRegistrationResponse.getClientId()); - updateQueryString(request); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(response); - assertThat(clientRegistrationResponse.getClientId()) - .isEqualTo(expectedClientRegistrationResponse.getClientId()); - assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isBetween( - expectedClientRegistrationResponse.getClientIdIssuedAt().minusSeconds(1), - expectedClientRegistrationResponse.getClientIdIssuedAt().plusSeconds(1)); - assertThat(clientRegistrationResponse.getClientSecret()) - .isEqualTo(expectedClientRegistrationResponse.getClientSecret()); - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()) - .isEqualTo(expectedClientRegistrationResponse.getClientSecretExpiresAt()); - assertThat(clientRegistrationResponse.getClientName()) - .isEqualTo(expectedClientRegistrationResponse.getClientName()); - assertThat(clientRegistrationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getRedirectUris()); - assertThat(clientRegistrationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getGrantTypes()); - assertThat(clientRegistrationResponse.getResponseTypes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getResponseTypes()); - assertThat(clientRegistrationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getScopes()); - assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod()); - assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientRegistrationResponse.getRegistrationClientUrl()) - .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUrl()); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws ServletException, IOException { - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - this.filter.setAuthenticationConverter(authenticationConverter); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client-id"); - updateQueryString(request); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(request); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - OidcClientRegistration expectedClientRegistrationResponse = createClientRegistration(); - Authentication principal = new TestingAuthenticationToken("principal", "Credentials"); - - OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult = new OidcClientRegistrationAuthenticationToken( - principal, expectedClientRegistrationResponse); - - given(this.authenticationManager.authenticate(any())).willReturn(clientRegistrationAuthenticationResult); - AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(successHandler); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, expectedClientRegistrationResponse.getClientId()); - updateQueryString(request); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(successHandler).onAuthenticationSuccess(request, response, clientRegistrationAuthenticationResult); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - given(this.authenticationManager.authenticate(any())) - .willThrow(new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN)); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1"); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationFailureHandler).onAuthenticationFailure(eq(request), eq(response), - any(OAuth2AuthenticationException.class)); - } - - private static void updateQueryString(MockHttpServletRequest request) { - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(request.getRequestURI()); - request.getParameterMap().forEach((key, values) -> { - if (values.length > 0) { - for (String value : values) { - uriBuilder.queryParam(key, value); - } - } - }); - request.setQueryString(uriBuilder.build().getQuery()); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private void writeClientRegistrationRequest(MockHttpServletRequest request, - OidcClientRegistration clientRegistration) throws Exception { - MockClientHttpRequest httpRequest = new MockClientHttpRequest(); - this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); - request.setContent(httpRequest.getBodyAsBytes()); - } - - private OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); - } - - private static OidcClientRegistration createClientRegistration() { - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientId("client-id") - .clientIdIssuedAt(Instant.now()) - .clientSecret("client-secret") - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) - .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) - .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .scope("scope1") - .scope("scope2") - .registrationClientUrl("https://auth-server:9000/connect/register?client_id=client-id") - .build(); - return clientRegistration; - // @formatter:on - } - - private static Jwt createJwt(String scope) { - // @formatter:off - JwsHeader jwsHeader = TestJwsHeaders.jwsHeader() - .build(); - JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet() - .claim(OAuth2ParameterNames.SCOPE, Collections.singleton(scope)) - .build(); - Jwt jwt = Jwt.withTokenValue("jwt-access-token") - .headers((headers) -> headers.putAll(jwsHeader.getHeaders())) - .claims((claims) -> claims.putAll(jwtClaimsSet.getClaims())) - .build(); - // @formatter:on - return jwt; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java deleted file mode 100644 index ff52a4294..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilterTests.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.oidc.web; - -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcLogoutEndpointFilter}. - * - * @author Joe Grandja - */ -public class OidcLogoutEndpointFilterTests { - - private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout"; - - private AuthenticationManager authenticationManager; - - private OidcLogoutEndpointFilter filter; - - private TestingAuthenticationToken principal; - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OidcLogoutEndpointFilter(this.authenticationManager); - this.principal = new TestingAuthenticationToken("principal", "credentials"); - this.principal.setAuthenticated(true); - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(this.principal); - SecurityContextHolder.setContext(securityContext); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OidcLogoutEndpointFilter(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenLogoutEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OidcLogoutEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("logoutEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotLogoutRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenLogoutRequestMissingIdTokenHintThenInvalidRequestError() throws Exception { - doFilterWhenRequestInvalidParameterThenError( - createLogoutRequest(TestRegisteredClients.registeredClient().build()), "id_token_hint", - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request.removeParameter("id_token_hint")); - } - - @Test - public void doFilterWhenLogoutRequestMultipleIdTokenHintThenInvalidRequestError() throws Exception { - doFilterWhenRequestInvalidParameterThenError( - createLogoutRequest(TestRegisteredClients.registeredClient().build()), "id_token_hint", - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request.addParameter("id_token_hint", "id-token-2")); - } - - @Test - public void doFilterWhenLogoutRequestMultipleClientIdThenInvalidRequestError() throws Exception { - doFilterWhenRequestInvalidParameterThenError( - createLogoutRequest(TestRegisteredClients.registeredClient().build()), OAuth2ParameterNames.CLIENT_ID, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2")); - } - - @Test - public void doFilterWhenLogoutRequestMultiplePostLogoutRedirectUriThenInvalidRequestError() throws Exception { - doFilterWhenRequestInvalidParameterThenError( - createLogoutRequest(TestRegisteredClients.registeredClient().build()), "post_logout_redirect_uri", - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter("post_logout_redirect_uri", "https://example.com/callback-4")); - } - - @Test - public void doFilterWhenLogoutRequestMultipleStateThenInvalidRequestError() throws Exception { - doFilterWhenRequestInvalidParameterThenError( - createLogoutRequest(TestRegisteredClients.registeredClient().build()), OAuth2ParameterNames.STATE, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.STATE, "state-2")); - } - - private void doFilterWhenRequestInvalidParameterThenError(MockHttpServletRequest request, String parameterName, - String errorCode, Consumer requestConsumer) throws Exception { - - requestConsumer.accept(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getErrorMessage()) - .isEqualTo("[" + errorCode + "] OpenID Connect 1.0 Logout Request Parameter: " + parameterName); - } - - @Test - public void doFilterWhenLogoutRequestAuthenticationExceptionThenErrorResponse() throws Exception { - OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri"); - given(this.authenticationManager.authenticate(any())).willThrow(new OAuth2AuthenticationException(error)); - - MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getErrorMessage()).isEqualTo(error.toString()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.principal); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", this.principal, - null, null, null, null); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn((authentication)); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(this.authenticationManager.authenticate(any())).willReturn((authentication)); - - MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", this.principal, - null, null, null, null); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - given(this.authenticationManager.authenticate(any())).willReturn((authentication)); - - MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), same(authentication)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - given(this.authenticationManager.authenticate(any())) - .willThrow(new AuthenticationServiceException("AuthenticationServiceException")); - - MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - ArgumentCaptor authenticationExceptionCaptor = ArgumentCaptor - .forClass(AuthenticationException.class); - verify(this.authenticationManager).authenticate(any()); - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), - authenticationExceptionCaptor.capture()); - verifyNoInteractions(filterChain); - - assertThat(authenticationExceptionCaptor.getValue()).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .satisfies((error) -> { - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - assertThat(error.getDescription()).contains("AuthenticationServiceException"); - }); - } - - @Test - public void doFilterWhenLogoutRequestAuthenticatedThenLogout() throws Exception { - MockHttpServletRequest request = createLogoutRequest(TestRegisteredClients.registeredClient().build()); - MockHttpSession session = (MockHttpSession) request.getSession(true); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", this.principal, - session.getId(), null, null, null); - - given(this.authenticationManager.authenticate(any())).willReturn((authentication)); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo("/"); - assertThat(session.isInvalid()).isTrue(); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } - - @Test - public void doFilterWhenLogoutRequestAuthenticatedWithPostLogoutRedirectUriThenPostLogoutRedirect() - throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - MockHttpServletRequest request = createLogoutRequest(registeredClient); - MockHttpSession session = (MockHttpSession) request.getSession(true); - - String postLogoutRedirectUri = registeredClient.getPostLogoutRedirectUris().iterator().next(); - String state = "state-1"; - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", this.principal, - session.getId(), registeredClient.getClientId(), postLogoutRedirectUri, state); - authentication.setAuthenticated(true); - - given(this.authenticationManager.authenticate(any())).willReturn((authentication)); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo(postLogoutRedirectUri + "?state=" + state); - assertThat(session.isInvalid()).isTrue(); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - } - - private static MockHttpServletRequest createLogoutRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_OIDC_LOGOUT_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - - request.addParameter("id_token_hint", "id-token"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - request.addParameter("post_logout_redirect_uri", - registeredClient.getPostLogoutRedirectUris().iterator().next()); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java deleted file mode 100644 index 467682d48..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.web.util.InvalidUrlException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcProviderConfigurationEndpointFilter}. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - */ -public class OidcProviderConfigurationEndpointFilterTests { - - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - - private final OidcProviderConfigurationEndpointFilter filter = new OidcProviderConfigurationEndpointFilter(); - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void setProviderConfigurationCustomizerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setProviderConfigurationCustomizer(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("providerConfigurationCustomizer cannot be null"); - } - - @Test - public void doFilterWhenNotConfigurationRequestThenNotProcessed() throws Exception { - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenConfigurationRequestPostThenNotProcessed() throws Exception { - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - - String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenConfigurationRequestThenConfigurationResponse() throws Exception { - String issuer = "https://example.com"; - String authorizationEndpoint = "/oauth2/v1/authorize"; - String pushedAuthorizationRequestEndpoint = "/oauth2/v1/par"; - String tokenEndpoint = "/oauth2/v1/token"; - String jwkSetEndpoint = "/oauth2/v1/jwks"; - String userInfoEndpoint = "/userinfo"; - String logoutEndpoint = "/connect/logout"; - String tokenRevocationEndpoint = "/oauth2/v1/revoke"; - String tokenIntrospectionEndpoint = "/oauth2/v1/introspect"; - - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer(issuer) - .authorizationEndpoint(authorizationEndpoint) - .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint) - .tokenEndpoint(tokenEndpoint) - .jwkSetEndpoint(jwkSetEndpoint) - .oidcUserInfoEndpoint(userInfoEndpoint) - .oidcLogoutEndpoint(logoutEndpoint) - .tokenRevocationEndpoint(tokenRevocationEndpoint) - .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint) - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - - String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - String providerConfigurationResponse = response.getContentAsString(); - assertThat(providerConfigurationResponse).contains("\"issuer\":\"https://example.com\""); - assertThat(providerConfigurationResponse) - .contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\""); - assertThat(providerConfigurationResponse) - .contains("\"pushed_authorization_request_endpoint\":\"https://example.com/oauth2/v1/par\""); - assertThat(providerConfigurationResponse) - .contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\""); - assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\""); - assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]"); - assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]"); - assertThat(providerConfigurationResponse).contains( - "\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]"); - assertThat(providerConfigurationResponse) - .contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\""); - assertThat(providerConfigurationResponse).contains( - "\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - assertThat(providerConfigurationResponse) - .contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\""); - assertThat(providerConfigurationResponse).contains( - "\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - assertThat(providerConfigurationResponse).contains("\"code_challenge_methods_supported\":[\"S256\"]"); - assertThat(providerConfigurationResponse).contains("\"tls_client_certificate_bound_access_tokens\":true"); - assertThat(providerConfigurationResponse).contains( - "\"dpop_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\",\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES384\",\"ES512\"]"); - assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]"); - assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]"); - assertThat(providerConfigurationResponse).contains("\"userinfo_endpoint\":\"https://example.com/userinfo\""); - assertThat(providerConfigurationResponse) - .contains("\"end_session_endpoint\":\"https://example.com/connect/logout\""); - assertThat(providerConfigurationResponse).contains( - "\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - } - - @Test - public void doFilterWhenAuthorizationServerSettingsWithInvalidIssuerThenThrowIllegalArgumentException() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://this is an invalid URL") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - - String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - assertThatThrownBy(() -> this.filter.doFilter(request, response, filterChain)) - .isInstanceOf(InvalidUrlException.class); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilterTests.java deleted file mode 100644 index d53eca48e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilterTests.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.oidc.web; - -import java.time.Instant; -import java.util.Collections; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -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; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OidcUserInfoEndpointFilter}. - * - * @author Steve Riesenberg - */ -public class OidcUserInfoEndpointFilterTests { - - private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo"; - - private AuthenticationManager authenticationManager; - - private OidcUserInfoEndpointFilter filter; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setup() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OidcUserInfoEndpointFilter(this.authenticationManager, DEFAULT_OIDC_USER_INFO_ENDPOINT_URI); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> new OidcUserInfoEndpointFilter(null)) - .withMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenUserInfoEndpointUriIsEmptyThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcUserInfoEndpointFilter(this.authenticationManager, "")) - .withMessage("userInfoEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .withMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .withMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .withMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotUserInfoRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(request, response); - } - - @Test - public void doFilterWhenUserInfoRequestPutThenNotProcessed() throws Exception { - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("PUT", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(this.authenticationManager); - verify(filterChain).doFilter(request, response); - } - - @Test - public void doFilterWhenUserInfoRequestGetThenSuccess() throws Exception { - doFilterWhenUserInfoRequestThenSuccess("GET"); - } - - @Test - public void doFilterWhenUserInfoRequestPostThenSuccess() throws Exception { - doFilterWhenUserInfoRequestThenSuccess("POST"); - } - - private void doFilterWhenUserInfoRequestThenSuccess(String httpMethod) throws Exception { - JwtAuthenticationToken principal = createJwtAuthenticationToken(); - SecurityContextHolder.getContext().setAuthentication(principal); - - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal, - createUserInfo()); - given(this.authenticationManager.authenticate(any())).willReturn(authentication); - - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest(httpMethod, requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - assertUserInfoResponse(response.getContentAsString()); - } - - @Test - public void doFilterWhenUserInfoRequestInvalidTokenThenUnauthorizedError() throws Exception { - doFilterWhenAuthenticationExceptionThenError(OAuth2ErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED); - } - - @Test - public void doFilterWhenUserInfoRequestInsufficientScopeThenForbiddenError() throws Exception { - doFilterWhenAuthenticationExceptionThenError(OAuth2ErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN); - } - - private void doFilterWhenAuthenticationExceptionThenError(String oauth2ErrorCode, HttpStatus httpStatus) - throws Exception { - Authentication principal = new TestingAuthenticationToken("principal", "credentials"); - SecurityContextHolder.getContext().setAuthentication(principal); - - given(this.authenticationManager.authenticate(any())) - .willThrow(new OAuth2AuthenticationException(oauth2ErrorCode)); - - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(httpStatus.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(oauth2ErrorCode); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - Authentication principal = new TestingAuthenticationToken("principal", "credentials"); - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal); - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(authenticationConverter.convert(any())).willReturn(authentication); - given(this.authenticationManager.authenticate(any())) - .willReturn(new OidcUserInfoAuthenticationToken(principal, createUserInfo())); - - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verify(authenticationConverter).convert(request); - verify(this.authenticationManager).authenticate(authentication); - assertUserInfoResponse(response.getContentAsString()); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(successHandler); - - Authentication principal = new TestingAuthenticationToken("principal", "credentials"); - SecurityContextHolder.getContext().setAuthentication(principal); - - OidcUserInfoAuthenticationToken authentication = new OidcUserInfoAuthenticationToken(principal, - createUserInfo()); - given(this.authenticationManager.authenticate(any())).willReturn(authentication); - - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verify(successHandler).onAuthenticationSuccess(request, response, authentication); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - AuthenticationFailureHandler failureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(failureHandler); - - Authentication principal = new TestingAuthenticationToken("principal", "credentials"); - SecurityContextHolder.getContext().setAuthentication(principal); - - OAuth2AuthenticationException authenticationException = new OAuth2AuthenticationException( - OAuth2ErrorCodes.INVALID_TOKEN); - given(this.authenticationManager.authenticate(any())).willThrow(authenticationException); - - String requestUri = DEFAULT_OIDC_USER_INFO_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verify(failureHandler).onAuthenticationFailure(request, response, authenticationException); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private JwtAuthenticationToken createJwtAuthenticationToken() { - Instant now = Instant.now(); - // @formatter:off - Jwt jwt = Jwt.withTokenValue("token") - .header(JoseHeaderNames.ALG, SignatureAlgorithm.RS256.getName()) - .issuedAt(now) - .expiresAt(now.plusSeconds(300)) - .claim(StandardClaimNames.SUB, "user") - .build(); - // @formatter:on - return new JwtAuthenticationToken(jwt, Collections.emptyList()); - } - - private static OidcUserInfo createUserInfo() { - return OidcUserInfo.builder() - .subject("user1") - .name("First Last") - .givenName("First") - .familyName("Last") - .middleName("Middle") - .nickname("User") - .preferredUsername("user") - .profile("https://example.com/user1") - .picture("https://example.com/user1.jpg") - .website("https://example.com") - .email("user1@example.com") - .emailVerified(true) - .gender("female") - .birthdate("1970-01-01") - .zoneinfo("Europe/Paris") - .locale("en-US") - .phoneNumber("+1 (604) 555-1234;ext=5678") - .phoneNumberVerified(false) - .address("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance") - .updatedAt("1970-01-01T00:00:00Z") - .build(); - } - - private static void assertUserInfoResponse(String userInfoResponse) { - assertThat(userInfoResponse).contains("\"sub\":\"user1\""); - assertThat(userInfoResponse).contains("\"name\":\"First Last\""); - assertThat(userInfoResponse).contains("\"given_name\":\"First\""); - assertThat(userInfoResponse).contains("\"family_name\":\"Last\""); - assertThat(userInfoResponse).contains("\"middle_name\":\"Middle\""); - assertThat(userInfoResponse).contains("\"nickname\":\"User\""); - assertThat(userInfoResponse).contains("\"preferred_username\":\"user\""); - assertThat(userInfoResponse).contains("\"profile\":\"https://example.com/user1\""); - assertThat(userInfoResponse).contains("\"picture\":\"https://example.com/user1.jpg\""); - assertThat(userInfoResponse).contains("\"website\":\"https://example.com\""); - assertThat(userInfoResponse).contains("\"email\":\"user1@example.com\""); - assertThat(userInfoResponse).contains("\"email_verified\":true"); - assertThat(userInfoResponse).contains("\"gender\":\"female\""); - assertThat(userInfoResponse).contains("\"birthdate\":\"1970-01-01\""); - assertThat(userInfoResponse).contains("\"zoneinfo\":\"Europe/Paris\""); - assertThat(userInfoResponse).contains("\"locale\":\"en-US\""); - assertThat(userInfoResponse).contains("\"phone_number\":\"+1 (604) 555-1234;ext=5678\""); - assertThat(userInfoResponse).contains("\"phone_number_verified\":false"); - assertThat(userInfoResponse) - .contains("\"address\":\"Champ de Mars\\n5 Av. Anatole France\\n75007 Paris\\nFrance\""); - assertThat(userInfoResponse).contains("\"updated_at\":\"1970-01-01T00:00:00Z\""); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandlerTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandlerTests.java deleted file mode 100644 index b39b78cc5..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandlerTests.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.oidc.web.authentication; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken; -import org.springframework.security.web.authentication.logout.LogoutHandler; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OidcLogoutAuthenticationSuccessHandler}. - * - * @author Joe Grandja - */ -public class OidcLogoutAuthenticationSuccessHandlerTests { - - private TestingAuthenticationToken principal; - - private final OidcLogoutAuthenticationSuccessHandler authenticationSuccessHandler = new OidcLogoutAuthenticationSuccessHandler(); - - @BeforeEach - public void setUp() { - this.principal = new TestingAuthenticationToken("principal", "credentials"); - this.principal.setAuthenticated(true); - } - - @Test - public void setLogoutHandlerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authenticationSuccessHandler.setLogoutHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("logoutHandler cannot be null"); - // @formatter:on - } - - @Test - public void onAuthenticationSuccessWhenInvalidAuthenticationTypeThenThrowOAuth2AuthenticationException() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - assertThatThrownBy( - () -> this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, this.principal)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - } - - @Test - public void onAuthenticationSuccessWhenLogoutHandlerSetThenUsed() throws Exception { - LogoutHandler logoutHandler = mock(LogoutHandler.class); - this.authenticationSuccessHandler.setLogoutHandler(logoutHandler); - - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpSession session = (MockHttpSession) request.getSession(true); - MockHttpServletResponse response = new MockHttpServletResponse(); - - OidcLogoutAuthenticationToken authentication = new OidcLogoutAuthenticationToken("id-token", this.principal, - session.getId(), null, null, null); - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication); - - verify(logoutHandler).logout(any(HttpServletRequest.class), any(HttpServletResponse.class), - any(Authentication.class)); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java deleted file mode 100644 index 551c78564..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link AuthorizationServerSettings}. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - */ -public class AuthorizationServerSettingsTests { - - @Test - public void buildWhenDefaultThenDefaultsAreSet() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - - assertThat(authorizationServerSettings.getIssuer()).isNull(); - assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isFalse(); - assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo("/oauth2/authorize"); - assertThat(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()).isEqualTo("/oauth2/par"); - assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo("/oauth2/token"); - assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo("/oauth2/jwks"); - assertThat(authorizationServerSettings.getTokenRevocationEndpoint()).isEqualTo("/oauth2/revoke"); - assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect"); - assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()).isEqualTo("/connect/register"); - assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo("/userinfo"); - assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo("/connect/logout"); - } - - @Test - public void buildWhenSettingsProvidedThenSet() { - String authorizationEndpoint = "/oauth2/v1/authorize"; - String pushedAuthorizationRequestEndpoint = "/oauth2/v1/par"; - String tokenEndpoint = "/oauth2/v1/token"; - String jwkSetEndpoint = "/oauth2/v1/jwks"; - String tokenRevocationEndpoint = "/oauth2/v1/revoke"; - String tokenIntrospectionEndpoint = "/oauth2/v1/introspect"; - String oidcClientRegistrationEndpoint = "/connect/v1/register"; - String oidcUserInfoEndpoint = "/connect/v1/userinfo"; - String oidcLogoutEndpoint = "/connect/v1/logout"; - String issuer = "https://example.com:9000"; - - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer(issuer) - .authorizationEndpoint(authorizationEndpoint) - .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint) - .tokenEndpoint(tokenEndpoint) - .jwkSetEndpoint(jwkSetEndpoint) - .tokenRevocationEndpoint(tokenRevocationEndpoint) - .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint) - .tokenRevocationEndpoint(tokenRevocationEndpoint) - .oidcClientRegistrationEndpoint(oidcClientRegistrationEndpoint) - .oidcUserInfoEndpoint(oidcUserInfoEndpoint) - .oidcLogoutEndpoint(oidcLogoutEndpoint) - .build(); - - assertThat(authorizationServerSettings.getIssuer()).isEqualTo(issuer); - assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isFalse(); - assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo(authorizationEndpoint); - assertThat(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) - .isEqualTo(pushedAuthorizationRequestEndpoint); - assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo(tokenEndpoint); - assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo(jwkSetEndpoint); - assertThat(authorizationServerSettings.getTokenRevocationEndpoint()).isEqualTo(tokenRevocationEndpoint); - assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo(tokenIntrospectionEndpoint); - assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()) - .isEqualTo(oidcClientRegistrationEndpoint); - assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo(oidcUserInfoEndpoint); - assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo(oidcLogoutEndpoint); - } - - @Test - public void buildWhenIssuerSetAndMultipleIssuersAllowedTrueThenThrowIllegalArgumentException() { - String issuer = "https://example.com:9000"; - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().issuer(issuer).multipleIssuersAllowed(true).build()) - .withMessage( - "The issuer identifier (" + issuer + ") cannot be set when isMultipleIssuersAllowed() is true."); - } - - @Test - public void buildWhenIssuerNotSetAndMultipleIssuersAllowedTrueThenDefaultsAreSet() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .multipleIssuersAllowed(true) - .build(); - - assertThat(authorizationServerSettings.getIssuer()).isNull(); - assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isTrue(); - assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo("/oauth2/authorize"); - assertThat(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()).isEqualTo("/oauth2/par"); - assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo("/oauth2/token"); - assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo("/oauth2/jwks"); - assertThat(authorizationServerSettings.getTokenRevocationEndpoint()).isEqualTo("/oauth2/revoke"); - assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect"); - assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()).isEqualTo("/connect/register"); - assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo("/userinfo"); - assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo("/connect/logout"); - } - - @Test - public void settingWhenCustomThenSet() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .setting("name1", "value1") - .settings((settings) -> settings.put("name2", "value2")) - .build(); - - assertThat(authorizationServerSettings.getSettings()).hasSize(14); - assertThat(authorizationServerSettings.getSetting("name1")).isEqualTo("value1"); - assertThat(authorizationServerSettings.getSetting("name2")).isEqualTo("value2"); - } - - @Test - public void issuerWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> AuthorizationServerSettings.builder().issuer(null)) - .withMessage("value cannot be null"); - } - - @Test - public void authorizationEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().authorizationEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void pushedAuthorizationRequestEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().pushedAuthorizationRequestEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void tokenEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> AuthorizationServerSettings.builder().tokenEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void tokenRevocationEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().tokenRevocationEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void tokenIntrospectionEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().tokenIntrospectionEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void oidcClientRegistrationEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().oidcClientRegistrationEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void oidcUserInfoEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().oidcUserInfoEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void jwksEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().jwkSetEndpoint(null)) - .withMessage("value cannot be null"); - } - - @Test - public void oidcLogoutEndpointWhenNullThenThrowIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> AuthorizationServerSettings.builder().oidcLogoutEndpoint(null)) - .withMessage("value cannot be null"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettingsTests.java deleted file mode 100644 index a80ea4771..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettingsTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ClientSettings}. - * - * @author Joe Grandja - */ -public class ClientSettingsTests { - - @Test - public void buildWhenDefaultThenDefaultsAreSet() { - ClientSettings clientSettings = ClientSettings.builder().build(); - assertThat(clientSettings.getSettings()).hasSize(2); - assertThat(clientSettings.isRequireProofKey()).isFalse(); - assertThat(clientSettings.isRequireAuthorizationConsent()).isFalse(); - } - - @Test - public void requireProofKeyWhenTrueThenSet() { - ClientSettings clientSettings = ClientSettings.builder().requireProofKey(true).build(); - assertThat(clientSettings.isRequireProofKey()).isTrue(); - } - - @Test - public void requireAuthorizationConsentWhenTrueThenSet() { - ClientSettings clientSettings = ClientSettings.builder().requireAuthorizationConsent(true).build(); - assertThat(clientSettings.isRequireAuthorizationConsent()).isTrue(); - } - - @Test - public void tokenEndpointAuthenticationSigningAlgorithmWhenHS256ThenSet() { - ClientSettings clientSettings = ClientSettings.builder() - .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256) - .build(); - assertThat(clientSettings.getTokenEndpointAuthenticationSigningAlgorithm()).isEqualTo(MacAlgorithm.HS256); - } - - @Test - public void jwkSetUrlWhenProvidedThenSet() { - ClientSettings clientSettings = ClientSettings.builder().jwkSetUrl("https://client.example.com/jwks").build(); - assertThat(clientSettings.getJwkSetUrl()).isEqualTo("https://client.example.com/jwks"); - } - - @Test - public void x509CertificateSubjectDNWhenProvidedThenSet() { - ClientSettings clientSettings = ClientSettings.builder() - .x509CertificateSubjectDN("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US") - .build(); - assertThat(clientSettings.getX509CertificateSubjectDN()) - .isEqualTo("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US"); - } - - @Test - public void settingWhenCustomThenSet() { - ClientSettings clientSettings = ClientSettings.builder() - .setting("name1", "value1") - .settings((settings) -> settings.put("name2", "value2")) - .build(); - assertThat(clientSettings.getSettings()).hasSize(4); - assertThat(clientSettings.getSetting("name1")).isEqualTo("value1"); - assertThat(clientSettings.getSetting("name2")).isEqualTo("value2"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java deleted file mode 100644 index 751f852b3..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettingsTests.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.settings; - -import java.time.Duration; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link TokenSettings}. - * - * @author Joe Grandja - */ -public class TokenSettingsTests { - - @Test - public void buildWhenDefaultThenDefaultsAreSet() { - TokenSettings tokenSettings = TokenSettings.builder().build(); - assertThat(tokenSettings.getSettings()).hasSize(8); - assertThat(tokenSettings.getAuthorizationCodeTimeToLive()).isEqualTo(Duration.ofMinutes(5)); - assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5)); - assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.SELF_CONTAINED); - assertThat(tokenSettings.getDeviceCodeTimeToLive()).isEqualTo(Duration.ofMinutes(5)); - assertThat(tokenSettings.isReuseRefreshTokens()).isTrue(); - assertThat(tokenSettings.getRefreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60)); - assertThat(tokenSettings.getIdTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256); - assertThat(tokenSettings.isX509CertificateBoundAccessTokens()).isFalse(); - } - - @Test - public void authorizationCodeTimeToLiveWhenProvidedThenSet() { - Duration authorizationCodeTimeToLive = Duration.ofMinutes(10); - TokenSettings tokenSettings = TokenSettings.builder() - .authorizationCodeTimeToLive(authorizationCodeTimeToLive) - .build(); - assertThat(tokenSettings.getAuthorizationCodeTimeToLive()).isEqualTo(authorizationCodeTimeToLive); - } - - @Test - public void authorizationCodeTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> TokenSettings.builder().authorizationCodeTimeToLive(null)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("authorizationCodeTimeToLive cannot be null"); - - assertThatThrownBy(() -> TokenSettings.builder().authorizationCodeTimeToLive(Duration.ZERO)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("authorizationCodeTimeToLive must be greater than Duration.ZERO"); - - assertThatThrownBy(() -> TokenSettings.builder().authorizationCodeTimeToLive(Duration.ofSeconds(-10))) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("authorizationCodeTimeToLive must be greater than Duration.ZERO"); - } - - @Test - public void accessTokenTimeToLiveWhenProvidedThenSet() { - Duration accessTokenTimeToLive = Duration.ofMinutes(10); - TokenSettings tokenSettings = TokenSettings.builder().accessTokenTimeToLive(accessTokenTimeToLive).build(); - assertThat(tokenSettings.getAccessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive); - } - - @Test - public void accessTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> TokenSettings.builder().accessTokenTimeToLive(null)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("accessTokenTimeToLive cannot be null"); - - assertThatThrownBy(() -> TokenSettings.builder().accessTokenTimeToLive(Duration.ZERO)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("accessTokenTimeToLive must be greater than Duration.ZERO"); - - assertThatThrownBy(() -> TokenSettings.builder().accessTokenTimeToLive(Duration.ofSeconds(-10))) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("accessTokenTimeToLive must be greater than Duration.ZERO"); - } - - @Test - public void accessTokenFormatWhenProvidedThenSet() { - TokenSettings tokenSettings = TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build(); - assertThat(tokenSettings.getAccessTokenFormat()).isEqualTo(OAuth2TokenFormat.REFERENCE); - } - - @Test - public void accessTokenFormatWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> TokenSettings.builder().accessTokenFormat(null)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("accessTokenFormat cannot be null"); - } - - @Test - public void reuseRefreshTokensWhenFalseThenSet() { - TokenSettings tokenSettings = TokenSettings.builder().reuseRefreshTokens(false).build(); - assertThat(tokenSettings.isReuseRefreshTokens()).isFalse(); - } - - @Test - public void refreshTokenTimeToLiveWhenProvidedThenSet() { - Duration refreshTokenTimeToLive = Duration.ofDays(10); - TokenSettings tokenSettings = TokenSettings.builder().refreshTokenTimeToLive(refreshTokenTimeToLive).build(); - assertThat(tokenSettings.getRefreshTokenTimeToLive()).isEqualTo(refreshTokenTimeToLive); - } - - @Test - public void refreshTokenTimeToLiveWhenNullOrZeroOrNegativeThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> TokenSettings.builder().refreshTokenTimeToLive(null)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("refreshTokenTimeToLive cannot be null"); - - assertThatThrownBy(() -> TokenSettings.builder().refreshTokenTimeToLive(Duration.ZERO)) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO"); - - assertThatThrownBy(() -> TokenSettings.builder().refreshTokenTimeToLive(Duration.ofSeconds(-10))) - .isInstanceOf(IllegalArgumentException.class) - .extracting(Throwable::getMessage) - .isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO"); - } - - @Test - public void idTokenSignatureAlgorithmWhenProvidedThenSet() { - SignatureAlgorithm idTokenSignatureAlgorithm = SignatureAlgorithm.RS512; - TokenSettings tokenSettings = TokenSettings.builder() - .idTokenSignatureAlgorithm(idTokenSignatureAlgorithm) - .build(); - assertThat(tokenSettings.getIdTokenSignatureAlgorithm()).isEqualTo(idTokenSignatureAlgorithm); - } - - @Test - public void x509CertificateBoundAccessTokensWhenTrueThenSet() { - TokenSettings tokenSettings = TokenSettings.builder().x509CertificateBoundAccessTokens(true).build(); - assertThat(tokenSettings.isX509CertificateBoundAccessTokens()).isTrue(); - } - - @Test - public void settingWhenCustomThenSet() { - TokenSettings tokenSettings = TokenSettings.builder() - .setting("name1", "value1") - .settings((settings) -> settings.put("name2", "value2")) - .build(); - assertThat(tokenSettings.getSettings()).hasSize(10); - assertThat(tokenSettings.getSetting("name1")).isEqualTo("value1"); - assertThat(tokenSettings.getSetting("name2")).isEqualTo("value2"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContext.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContext.java deleted file mode 100644 index f0464a11c..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContext.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.test; - -import java.io.Closeable; -import java.util.ArrayList; -import java.util.List; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; -import org.springframework.mock.web.MockServletConfig; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.BeanIds; -import org.springframework.test.context.web.GenericXmlWebContextLoader; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.test.web.servlet.setup.MockMvcConfigurer; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.context.support.XmlWebApplicationContext; -import org.springframework.web.filter.OncePerRequestFilter; - -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; - -/** - * @author Rob Winch - */ -public class SpringTestContext implements Closeable { - - private Object test; - - private ConfigurableWebApplicationContext context; - - private List filters = new ArrayList<>(); - - public void setTest(Object test) { - this.test = test; - } - - @Override - public void close() { - try { - this.context.close(); - } - catch (Exception ex) { - } - } - - public SpringTestContext context(ConfigurableWebApplicationContext context) { - this.context = context; - return this; - } - - public SpringTestContext register(Class... classes) { - AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); - applicationContext.register(classes); - this.context = applicationContext; - return this; - } - - public SpringTestContext testConfigLocations(String... configLocations) { - GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader(); - String[] locations = loader.processLocations(this.test.getClass(), configLocations); - return configLocations(locations); - } - - public SpringTestContext configLocations(String... configLocations) { - XmlWebApplicationContext context = new XmlWebApplicationContext(); - context.setConfigLocations(configLocations); - this.context = context; - return this; - } - - public SpringTestContext mockMvcAfterSpringSecurityOk() { - return addFilter(new OncePerRequestFilter() { - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) { - response.setStatus(HttpServletResponse.SC_OK); - } - }); - } - - private SpringTestContext addFilter(Filter filter) { - this.filters.add(filter); - return this; - } - - public ConfigurableWebApplicationContext getContext() { - if (!this.context.isRunning()) { - this.context.setServletContext(new MockServletContext()); - this.context.setServletConfig(new MockServletConfig()); - this.context.refresh(); - } - return this.context; - } - - public void autowire() { - this.context.setServletContext(new MockServletContext()); - this.context.setServletConfig(new MockServletConfig()); - this.context.refresh(); - - if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) { - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(springSecurity()) - .apply(new AddFilter()) - .build(); - this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc); - } - - AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); - bpp.setBeanFactory(this.context.getBeanFactory()); - bpp.processInjection(this.test); - } - - public class AddFilter implements MockMvcConfigurer { - - public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, - WebApplicationContext context) { - builder.addFilters(SpringTestContext.this.filters.toArray(new Filter[0])); - return null; - } - - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContextExtension.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContextExtension.java deleted file mode 100644 index 27adabaa8..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/test/SpringTestContextExtension.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.test; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -import org.springframework.security.test.context.TestSecurityContextHolder; - -public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - Object testInstance = context.getRequiredTestInstance(); - getContexts(testInstance).forEach((springTestContext) -> springTestContext.setTest(testInstance)); - } - - @Override - public void afterEach(ExtensionContext context) throws Exception { - TestSecurityContextHolder.clearContext(); - Object testInstance = context.getRequiredTestInstance(); - getContexts(testInstance).forEach(SpringTestContext::close); - } - - private static List getContexts(Object test) throws IllegalAccessException { - Field[] declaredFields = test.getClass().getDeclaredFields(); - List result = new ArrayList<>(); - for (Field field : declaredFields) { - if (SpringTestContext.class.isAssignableFrom(field.getType())) { - result.add((SpringTestContext) field.get(test)); - } - } - return result; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGeneratorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGeneratorTests.java deleted file mode 100644 index 7ca1c6b71..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGeneratorTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.time.Instant; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2Token; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link DelegatingOAuth2TokenGenerator}. - * - * @author Joe Grandja - */ -public class DelegatingOAuth2TokenGeneratorTests { - - @Test - @SuppressWarnings("unchecked") - public void constructorWhenTokenGeneratorsEmptyThenThrowIllegalArgumentException() { - OAuth2TokenGenerator[] tokenGenerators = new OAuth2TokenGenerator[0]; - assertThatThrownBy(() -> new DelegatingOAuth2TokenGenerator(tokenGenerators)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenGenerators cannot be empty"); - } - - @Test - public void constructorWhenTokenGeneratorsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new DelegatingOAuth2TokenGenerator(null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenGenerator cannot be null"); - } - - @Test - @SuppressWarnings("unchecked") - public void generateWhenTokenGeneratorSupportedThenReturnToken() { - OAuth2TokenGenerator tokenGenerator1 = mock(OAuth2TokenGenerator.class); - OAuth2TokenGenerator tokenGenerator2 = mock(OAuth2TokenGenerator.class); - OAuth2TokenGenerator tokenGenerator3 = mock(OAuth2TokenGenerator.class); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - Instant.now(), Instant.now().plusSeconds(300)); - given(tokenGenerator3.generate(any())).willReturn(accessToken); - - DelegatingOAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator(tokenGenerator1, - tokenGenerator2, tokenGenerator3); - - OAuth2Token token = delegatingTokenGenerator.generate(DefaultOAuth2TokenContext.builder().build()); - assertThat(token).isEqualTo(accessToken); - } - - @Test - @SuppressWarnings("unchecked") - public void generateWhenTokenGeneratorNotSupportedThenReturnNull() { - OAuth2TokenGenerator tokenGenerator1 = mock(OAuth2TokenGenerator.class); - OAuth2TokenGenerator tokenGenerator2 = mock(OAuth2TokenGenerator.class); - OAuth2TokenGenerator tokenGenerator3 = mock(OAuth2TokenGenerator.class); - - DelegatingOAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator(tokenGenerator1, - tokenGenerator2, tokenGenerator3); - - OAuth2Token token = delegatingTokenGenerator.generate(DefaultOAuth2TokenContext.builder().build()); - assertThat(token).isNull(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContextTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContextTests.java deleted file mode 100644 index 6625f94b1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContextTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.TestJwsHeaders; -import org.springframework.security.oauth2.jwt.TestJwtClaimsSets; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link JwtEncodingContext}. - * - * @author Joe Grandja - */ -public class JwtEncodingContextTests { - - @Test - public void withWhenJwsHeaderNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> JwtEncodingContext.with(null, TestJwtClaimsSets.jwtClaimsSet())) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwsHeaderBuilder cannot be null"); - } - - @Test - public void withWhenClaimsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> JwtEncodingContext.with(TestJwsHeaders.jwsHeader(), null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("claimsBuilder cannot be null"); - } - - @Test - public void setWhenValueNullThenThrowIllegalArgumentException() { - JwtEncodingContext.Builder builder = JwtEncodingContext.with(TestJwsHeaders.jwsHeader(), - TestJwtClaimsSets.jwtClaimsSet()); - assertThatThrownBy(() -> builder.registeredClient(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.principal(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.authorization(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.tokenType(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.authorizationGrantType(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.authorizationGrant(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> builder.put(null, "")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenAllValuesProvidedThenAllValuesAreSet() { - JwsHeader.Builder headers = TestJwsHeaders.jwsHeader(); - JwtClaimsSet.Builder claims = TestJwtClaimsSets.jwtClaimsSet(); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "password"); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authorizationGrant = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, authorizationRequest.getRedirectUri(), null); - - JwtEncodingContext context = JwtEncodingContext.with(headers, claims) - .registeredClient(registeredClient) - .principal(principal) - .authorization(authorization) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authorizationGrant) - .put("custom-key-1", "custom-value-1") - .context((ctx) -> ctx.put("custom-key-2", "custom-value-2")) - .build(); - - assertThat(context.getJwsHeader()).isEqualTo(headers); - assertThat(context.getClaims()).isEqualTo(claims); - assertThat(context.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(context.getPrincipal()).isEqualTo(principal); - assertThat(context.getAuthorization()).isEqualTo(authorization); - assertThat(context.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(context.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(context.getAuthorizationGrant()) - .isEqualTo(authorizationGrant); - assertThat(context.get("custom-key-1")).isEqualTo("custom-value-1"); - assertThat(context.get("custom-key-2")).isEqualTo("custom-value-2"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtGeneratorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtGeneratorTests.java deleted file mode 100644 index c457cf7d1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/JwtGeneratorTests.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.token; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link JwtGenerator}. - * - * @author Joe Grandja - */ -public class JwtGeneratorTests { - - private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN); - - private JwtEncoder jwtEncoder; - - private OAuth2TokenCustomizer jwtCustomizer; - - private JwtGenerator jwtGenerator; - - private TestAuthorizationServerContext authorizationServerContext; - - @BeforeEach - public void setUp() { - this.jwtEncoder = mock(JwtEncoder.class); - this.jwtCustomizer = mock(OAuth2TokenCustomizer.class); - this.jwtGenerator = new JwtGenerator(this.jwtEncoder); - this.jwtGenerator.setJwtCustomizer(this.jwtCustomizer); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - this.authorizationServerContext = new TestAuthorizationServerContext(authorizationServerSettings, null); - } - - @Test - public void constructorWhenJwtEncoderNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new JwtGenerator(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwtEncoder cannot be null"); - } - - @Test - public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.jwtGenerator.setJwtCustomizer(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwtCustomizer cannot be null"); - } - - @Test - public void generateWhenUnsupportedTokenTypeThenReturnNull() { - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .tokenType(new OAuth2TokenType("unsupported_token_type")) - .build(); - // @formatter:on - - assertThat(this.jwtGenerator.generate(tokenContext)).isNull(); - } - - @Test - public void generateWhenUnsupportedTokenFormatThenReturnNull() { - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(new OAuth2TokenFormat("unsupported_token_format")) - .build(); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .build(); - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .build(); - // @formatter:on - - assertThat(this.jwtGenerator.generate(tokenContext)).isNull(); - } - - @Test - public void generateWhenAccessTokenTypeThenReturnJwt() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, authorizationRequest.getRedirectUri(), null); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(authorization.getAttribute(Principal.class.getName())) - .authorizationServerContext(this.authorizationServerContext) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authentication) - .build(); - // @formatter:on - - assertGeneratedTokenType(tokenContext); - } - - @Test - public void generateWhenIdTokenTypeAndAuthorizationCodeGrantThenReturnJwt() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(OidcScopes.OPENID) - .tokenSettings(TokenSettings.builder().idTokenSignatureAlgorithm(SignatureAlgorithm.ES256).build()) - .build(); - Map authenticationRequestAdditionalParameters = new HashMap<>(); - authenticationRequestAdditionalParameters.put(OidcParameterNames.NONCE, "nonce"); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, authenticationRequestAdditionalParameters) - .build(); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, authorizationRequest.getRedirectUri(), null); - - Authentication principal = authorization.getAttribute(Principal.class.getName()); - SessionInformation sessionInformation = new SessionInformation(principal.getPrincipal(), "session1", - Date.from(Instant.now().minus(2, ChronoUnit.HOURS))); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(this.authorizationServerContext) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokenType(ID_TOKEN_TOKEN_TYPE) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authentication) - .put(SessionInformation.class, sessionInformation) - .build(); - // @formatter:on - - assertGeneratedTokenType(tokenContext); - } - - // gh-1224 - @Test - public void generateWhenIdTokenTypeAndRefreshTokenGrantThenReturnJwt() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject("subject") - .issuedAt(Instant.now()) - .expiresAt(Instant.now().plusSeconds(60)) - .claim("sid", "sessionId-1234") - .claim(IdTokenClaimNames.AUTH_TIME, Date.from(Instant.now())) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(idToken) - .build(); - - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - refreshToken.getTokenValue(), clientPrincipal, null, null); - - Authentication principal = authorization.getAttribute(Principal.class.getName()); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(this.authorizationServerContext) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokenType(ID_TOKEN_TOKEN_TYPE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrant(authentication) - .build(); - // @formatter:on - - assertGeneratedTokenType(tokenContext); - } - - // gh-1283 - @Test - public void generateWhenIdTokenTypeWithoutSidAndRefreshTokenGrantThenReturnJwt() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .issuer("https://provider.com") - .subject("subject") - .issuedAt(Instant.now()) - .expiresAt(Instant.now().plusSeconds(60)) - .build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .token(idToken) - .build(); - - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken( - refreshToken.getTokenValue(), clientPrincipal, null, null); - - Authentication principal = authorization.getAttribute(Principal.class.getName()); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(this.authorizationServerContext) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokenType(ID_TOKEN_TOKEN_TYPE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrant(authentication) - .build(); - // @formatter:on - - assertGeneratedTokenType(tokenContext); - } - - private void assertGeneratedTokenType(OAuth2TokenContext tokenContext) { - this.jwtGenerator.generate(tokenContext); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(this.jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getJwsHeader()).isNotNull(); - assertThat(jwtEncodingContext.getClaims()).isNotNull(); - assertThat(jwtEncodingContext.getRegisteredClient()).isEqualTo(tokenContext.getRegisteredClient()); - assertThat(jwtEncodingContext.getPrincipal()).isEqualTo(tokenContext.getPrincipal()); - assertThat(jwtEncodingContext.getAuthorization()).isEqualTo(tokenContext.getAuthorization()); - assertThat(jwtEncodingContext.getAuthorizedScopes()).isEqualTo(tokenContext.getAuthorizedScopes()); - assertThat(jwtEncodingContext.getTokenType()).isEqualTo(tokenContext.getTokenType()); - assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(tokenContext.getAuthorizationGrantType()); - assertThat(jwtEncodingContext.getAuthorizationGrant()) - .isEqualTo(tokenContext.getAuthorizationGrant()); - - ArgumentCaptor jwtEncoderParametersCaptor = ArgumentCaptor - .forClass(JwtEncoderParameters.class); - verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture()); - - JwsHeader jwsHeader = jwtEncoderParametersCaptor.getValue().getJwsHeader(); - if (OidcParameterNames.ID_TOKEN.equals(tokenContext.getTokenType().getValue())) { - assertThat(jwsHeader.getAlgorithm()) - .isEqualTo(tokenContext.getRegisteredClient().getTokenSettings().getIdTokenSignatureAlgorithm()); - } - else { - assertThat(jwsHeader.getAlgorithm()).isEqualTo(SignatureAlgorithm.RS256); - } - - JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims(); - assertThat(jwtClaimsSet.getIssuer().toExternalForm()) - .isEqualTo(tokenContext.getAuthorizationServerContext().getIssuer()); - assertThat(jwtClaimsSet.getSubject()).isEqualTo(tokenContext.getAuthorization().getPrincipalName()); - assertThat(jwtClaimsSet.getAudience()).containsExactly(tokenContext.getRegisteredClient().getClientId()); - - Instant issuedAt = Instant.now(); - Instant expiresAt; - if (tokenContext.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) { - expiresAt = issuedAt.plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive()); - } - else { - expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - } - assertThat(jwtClaimsSet.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - assertThat(jwtClaimsSet.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1)); - assertThat(jwtClaimsSet.getId()).isNotNull(); - - if (tokenContext.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) { - assertThat(jwtClaimsSet.getNotBefore()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - - Set scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE); - assertThat(scopes).isEqualTo(tokenContext.getAuthorizedScopes()); - } - else { - assertThat(jwtClaimsSet.getClaim(IdTokenClaimNames.AZP)) - .isEqualTo(tokenContext.getRegisteredClient().getClientId()); - if (tokenContext.getAuthorizationGrantType().equals(AuthorizationGrantType.AUTHORIZATION_CODE)) { - OAuth2AuthorizationRequest authorizationRequest = tokenContext.getAuthorization() - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE); - assertThat(jwtClaimsSet.getClaim(IdTokenClaimNames.NONCE)).isEqualTo(nonce); - - SessionInformation sessionInformation = tokenContext.get(SessionInformation.class); - assertThat(jwtClaimsSet.getClaim("sid")).isEqualTo(sessionInformation.getSessionId()); - assertThat(jwtClaimsSet.getClaim(IdTokenClaimNames.AUTH_TIME)) - .isEqualTo(sessionInformation.getLastRequest()); - } - else if (tokenContext.getAuthorizationGrantType().equals(AuthorizationGrantType.REFRESH_TOKEN)) { - OidcIdToken currentIdToken = tokenContext.getAuthorization().getToken(OidcIdToken.class).getToken(); - assertThat(jwtClaimsSet.getClaim("sid")).isEqualTo(currentIdToken.getClaim("sid")); - assertThat(jwtClaimsSet.getClaim(IdTokenClaimNames.AUTH_TIME)) - .isEqualTo(currentIdToken.getClaim(IdTokenClaimNames.AUTH_TIME)); - } - } - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java deleted file mode 100644 index 227c2784e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.security.Principal; -import java.time.Instant; -import java.util.Collections; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClaimAccessor; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2AccessTokenGenerator}. - * - * @author Joe Grandja - */ -public class OAuth2AccessTokenGeneratorTests { - - private OAuth2TokenCustomizer accessTokenCustomizer; - - private OAuth2AccessTokenGenerator accessTokenGenerator; - - private AuthorizationServerContext authorizationServerContext; - - @BeforeEach - public void setUp() { - this.accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - this.accessTokenGenerator = new OAuth2AccessTokenGenerator(); - this.accessTokenGenerator.setAccessTokenCustomizer(this.accessTokenCustomizer); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://provider.com") - .build(); - this.authorizationServerContext = new TestAuthorizationServerContext(authorizationServerSettings, null); - } - - @Test - public void setAccessTokenCustomizerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.accessTokenGenerator.setAccessTokenCustomizer(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("accessTokenCustomizer cannot be null"); - } - - @Test - public void generateWhenUnsupportedTokenTypeThenReturnNull() { - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build(); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .build(); - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .tokenType(new OAuth2TokenType("unsupported_token_type")) - .build(); - // @formatter:on - - assertThat(this.accessTokenGenerator.generate(tokenContext)).isNull(); - } - - @Test - public void generateWhenUnsupportedTokenFormatThenReturnNull() { - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(new OAuth2TokenFormat("unsupported_token_format")) - .build(); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .build(); - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .build(); - // @formatter:on - - assertThat(this.accessTokenGenerator.generate(tokenContext)).isNull(); - } - - @Test - public void generateWhenReferenceAccessTokenTypeThenReturnAccessToken() { - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build(); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .build(); - // @formatter:on - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, authorizationRequest.getRedirectUri(), null); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(this.authorizationServerContext) - .authorization(authorization) - .authorizedScopes(authorization.getAuthorizedScopes()) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authentication) - .build(); - // @formatter:on - - OAuth2AccessToken accessToken = this.accessTokenGenerator.generate(tokenContext); - assertThat(accessToken).isNotNull(); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive()); - assertThat(accessToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - assertThat(accessToken.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1)); - assertThat(accessToken.getScopes()).isEqualTo(tokenContext.getAuthorizedScopes()); - - assertThat(accessToken).isInstanceOf(ClaimAccessor.class); - OAuth2TokenClaimAccessor accessTokenClaims = ((ClaimAccessor) accessToken)::getClaims; - assertThat(accessTokenClaims.getClaims()).isNotEmpty(); - - assertThat(accessTokenClaims.getIssuer().toExternalForm()) - .isEqualTo(tokenContext.getAuthorizationServerContext().getIssuer()); - assertThat(accessTokenClaims.getSubject()).isEqualTo(tokenContext.getPrincipal().getName()); - assertThat(accessTokenClaims.getAudience()) - .isEqualTo(Collections.singletonList(tokenContext.getRegisteredClient().getClientId())); - assertThat(accessTokenClaims.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - assertThat(accessTokenClaims.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1)); - assertThat(accessTokenClaims.getNotBefore()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - assertThat(accessTokenClaims.getId()).isNotNull(); - - Set scopes = accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE); - assertThat(scopes).isEqualTo(tokenContext.getAuthorizedScopes()); - - ArgumentCaptor tokenClaimsContextCaptor = ArgumentCaptor - .forClass(OAuth2TokenClaimsContext.class); - verify(this.accessTokenCustomizer).customize(tokenClaimsContextCaptor.capture()); - - OAuth2TokenClaimsContext tokenClaimsContext = tokenClaimsContextCaptor.getValue(); - assertThat(tokenClaimsContext.getClaims()).isNotNull(); - assertThat(tokenClaimsContext.getRegisteredClient()).isEqualTo(tokenContext.getRegisteredClient()); - assertThat(tokenClaimsContext.getPrincipal()).isEqualTo(tokenContext.getPrincipal()); - assertThat(tokenClaimsContext.getAuthorizationServerContext()) - .isEqualTo(tokenContext.getAuthorizationServerContext()); - assertThat(tokenClaimsContext.getAuthorization()).isEqualTo(tokenContext.getAuthorization()); - assertThat(tokenClaimsContext.getAuthorizedScopes()).isEqualTo(tokenContext.getAuthorizedScopes()); - assertThat(tokenClaimsContext.getTokenType()).isEqualTo(tokenContext.getTokenType()); - assertThat(tokenClaimsContext.getAuthorizationGrantType()).isEqualTo(tokenContext.getAuthorizationGrantType()); - assertThat(tokenClaimsContext.getAuthorizationGrant()) - .isEqualTo(tokenContext.getAuthorizationGrant()); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java deleted file mode 100644 index ac32db3e6..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.time.Instant; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2RefreshTokenGenerator}. - * - * @author Joe Grandja - */ -public class OAuth2RefreshTokenGeneratorTests { - - private final OAuth2RefreshTokenGenerator tokenGenerator = new OAuth2RefreshTokenGenerator(); - - @Test - public void generateWhenUnsupportedTokenTypeThenReturnNull() { - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .build(); - // @formatter:on - - assertThat(this.tokenGenerator.generate(tokenContext)).isNull(); - } - - @Test - public void generateWhenRefreshTokenTypeThenReturnRefreshToken() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - // @formatter:off - OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder() - .registeredClient(registeredClient) - .tokenType(OAuth2TokenType.REFRESH_TOKEN) - .build(); - // @formatter:on - - OAuth2RefreshToken refreshToken = this.tokenGenerator.generate(tokenContext); - assertThat(refreshToken).isNotNull(); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(tokenContext.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); - assertThat(refreshToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); - assertThat(refreshToken.getExpiresAt()).isBetween(expiresAt.minusSeconds(1), expiresAt.plusSeconds(1)); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContextTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContextTests.java deleted file mode 100644 index cd5a16589..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContextTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.security.Principal; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenClaimsContext}. - * - * @author Joe Grandja - */ -public class OAuth2TokenClaimsContextTests { - - @Test - public void withWhenClaimsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2TokenClaimsContext.with(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("claimsBuilder cannot be null"); - } - - @Test - public void buildWhenAllValuesProvidedThenAllValuesAreSet() { - String issuer = "https://provider.com"; - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - - // @formatter:off - OAuth2TokenClaimsSet.Builder claims = OAuth2TokenClaimsSet.builder() - .issuer(issuer) - .subject("subject") - .audience(Collections.singletonList("client-1")) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .id("id"); - // @formatter:on - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer(issuer) - .build(); - AuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, null); - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationCodeAuthenticationToken authorizationGrant = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, authorizationRequest.getRedirectUri(), null); - - // @formatter:off - OAuth2TokenClaimsContext context = OAuth2TokenClaimsContext.with(claims) - .registeredClient(registeredClient) - .principal(principal) - .authorizationServerContext(authorizationServerContext) - .authorization(authorization) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrant(authorizationGrant) - .put("custom-key-1", "custom-value-1") - .context((ctx) -> ctx.put("custom-key-2", "custom-value-2")) - .build(); - // @formatter:on - - assertThat(context.getClaims()).isEqualTo(claims); - assertThat(context.getRegisteredClient()).isEqualTo(registeredClient); - assertThat(context.getPrincipal()).isEqualTo(principal); - assertThat(context.getAuthorizationServerContext()).isEqualTo(authorizationServerContext); - assertThat(context.getAuthorization()).isEqualTo(authorization); - assertThat(context.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN); - assertThat(context.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(context.getAuthorizationGrant()) - .isEqualTo(authorizationGrant); - assertThat(context.get("custom-key-1")).isEqualTo("custom-value-1"); - assertThat(context.get("custom-key-2")).isEqualTo("custom-value-2"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSetTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSetTests.java deleted file mode 100644 index ea88dfead..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsSetTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.token; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2TokenClaimsSet}. - * - * @author Joe Grandja - */ -public class OAuth2TokenClaimsSetTests { - - @Test - public void buildWhenClaimsEmptyThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().build()).isInstanceOf(IllegalArgumentException.class) - .hasMessage("claims cannot be empty"); - } - - @Test - public void buildWhenAllClaimsProvidedThenAllClaimsAreSet() { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - String customClaimName = "custom-claim-name"; - String customClaimValue = "custom-claim-value"; - - // @formatter:off - OAuth2TokenClaimsSet expectedClaimsSet = OAuth2TokenClaimsSet.builder() - .issuer("https://provider.com") - .subject("subject") - .audience(Collections.singletonList("client-1")) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .id("id") - .claims((claims) -> claims.put(customClaimName, customClaimValue)) - .build(); - - OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder() - .issuer(expectedClaimsSet.getIssuer().toExternalForm()) - .subject(expectedClaimsSet.getSubject()) - .audience(expectedClaimsSet.getAudience()) - .issuedAt(expectedClaimsSet.getIssuedAt()) - .notBefore(expectedClaimsSet.getNotBefore()) - .expiresAt(expectedClaimsSet.getExpiresAt()) - .id(expectedClaimsSet.getId()) - .claims((claims) -> claims.put(customClaimName, expectedClaimsSet.getClaim(customClaimName))) - .build(); - // @formatter:on - - assertThat(claimsSet.getIssuer()).isEqualTo(expectedClaimsSet.getIssuer()); - assertThat(claimsSet.getSubject()).isEqualTo(expectedClaimsSet.getSubject()); - assertThat(claimsSet.getAudience()).isEqualTo(expectedClaimsSet.getAudience()); - assertThat(claimsSet.getIssuedAt()).isEqualTo(expectedClaimsSet.getIssuedAt()); - assertThat(claimsSet.getNotBefore()).isEqualTo(expectedClaimsSet.getNotBefore()); - assertThat(claimsSet.getExpiresAt()).isEqualTo(expectedClaimsSet.getExpiresAt()); - assertThat(claimsSet.getId()).isEqualTo(expectedClaimsSet.getId()); - assertThat(claimsSet.getClaim(customClaimName)).isEqualTo(expectedClaimsSet.getClaim(customClaimName)); - assertThat(claimsSet.getClaims()).isEqualTo(expectedClaimsSet.getClaims()); - } - - @Test - public void claimWhenNameNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().claim(null, "value")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("name cannot be empty"); - } - - @Test - public void claimWhenValueNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> OAuth2TokenClaimsSet.builder().claim("name", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("value cannot be null"); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/TestX509Certificates.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/TestX509Certificates.java deleted file mode 100644 index 718431e82..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/TestX509Certificates.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.util; - -import java.security.KeyPair; -import java.security.cert.X509Certificate; - -/** - * @author Joe Grandja - */ -public final class TestX509Certificates { - - public static final X509Certificate[] DEMO_CLIENT_PKI_CERTIFICATE; - static { - try { - // Generate the Root certificate (Trust Anchor or most-trusted CA) - KeyPair rootKeyPair = X509CertificateUtils.generateRSAKeyPair(); - String distinguishedName = "CN=spring-samples-trusted-ca, OU=Spring Samples, O=Spring, C=US"; - X509Certificate rootCertificate = X509CertificateUtils.createTrustAnchorCertificate(rootKeyPair, - distinguishedName); - - // Generate the CA (intermediary) certificate - KeyPair caKeyPair = X509CertificateUtils.generateRSAKeyPair(); - distinguishedName = "CN=spring-samples-ca, OU=Spring Samples, O=Spring, C=US"; - X509Certificate caCertificate = X509CertificateUtils.createCACertificate(rootCertificate, - rootKeyPair.getPrivate(), caKeyPair.getPublic(), distinguishedName); - - // Generate certificate for demo-client-sample - KeyPair demoClientKeyPair = X509CertificateUtils.generateRSAKeyPair(); - distinguishedName = "CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US"; - X509Certificate demoClientCertificate = X509CertificateUtils.createEndEntityCertificate(caCertificate, - caKeyPair.getPrivate(), demoClientKeyPair.getPublic(), distinguishedName); - - DEMO_CLIENT_PKI_CERTIFICATE = new X509Certificate[] { demoClientCertificate, caCertificate, - rootCertificate }; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - public static final X509Certificate[] DEMO_CLIENT_SELF_SIGNED_CERTIFICATE; - static { - try { - // Generate self-signed certificate for demo-client-sample - KeyPair keyPair = X509CertificateUtils.generateRSAKeyPair(); - String distinguishedName = "CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US"; - X509Certificate demoClientSelfSignedCertificate = X509CertificateUtils.createTrustAnchorCertificate(keyPair, - distinguishedName); - - DEMO_CLIENT_SELF_SIGNED_CERTIFICATE = new X509Certificate[] { demoClientSelfSignedCertificate }; - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private TestX509Certificates() { - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/X509CertificateUtils.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/X509CertificateUtils.java deleted file mode 100644 index 878c78a50..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/util/X509CertificateUtils.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.util; - -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Security; -import java.security.cert.X509Certificate; -import java.security.spec.RSAKeyGenParameterSpec; -import java.util.Calendar; -import java.util.Date; - -import javax.security.auth.x500.X500Principal; - -import org.bouncycastle.asn1.x509.BasicConstraints; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.KeyUsage; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; - -/** - * @author Joe Grandja - */ -public final class X509CertificateUtils { - - private static final String BC_PROVIDER = "BC"; - - private static final String SHA256_RSA_SIGNATURE_ALGORITHM = "SHA256withRSA"; - - private static final Date DEFAULT_START_DATE; - - private static final Date DEFAULT_END_DATE; - - static { - Security.addProvider(new BouncyCastleProvider()); - - // Setup default certificate start date to yesterday and end date for 1 year - // validity - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.DATE, -1); - DEFAULT_START_DATE = calendar.getTime(); - calendar.add(Calendar.YEAR, 1); - DEFAULT_END_DATE = calendar.getTime(); - } - - private X509CertificateUtils() { - } - - public static KeyPair generateRSAKeyPair() { - KeyPair keyPair; - try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BC_PROVIDER); - keyPairGenerator.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)); - keyPair = keyPairGenerator.generateKeyPair(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - return keyPair; - } - - public static X509Certificate createTrustAnchorCertificate(KeyPair keyPair, String distinguishedName) - throws Exception { - X500Principal subject = new X500Principal(distinguishedName); - BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); - - X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(subject, serialNum, DEFAULT_START_DATE, - DEFAULT_END_DATE, subject, keyPair.getPublic()); - - // Add Extensions - JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils(); - certBuilder - // A BasicConstraints to mark root certificate as CA certificate - .addExtension(Extension.basicConstraints, true, new BasicConstraints(true)) - .addExtension(Extension.subjectKeyIdentifier, false, - extensionUtils.createSubjectKeyIdentifier(keyPair.getPublic())); - - ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER) - .build(keyPair.getPrivate()); - - JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER); - - return converter.getCertificate(certBuilder.build(signer)); - } - - public static X509Certificate createCACertificate(X509Certificate signerCert, PrivateKey signerKey, - PublicKey certKey, String distinguishedName) throws Exception { - - X500Principal subject = new X500Principal(distinguishedName); - BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); - - X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(signerCert.getSubjectX500Principal(), - serialNum, DEFAULT_START_DATE, DEFAULT_END_DATE, subject, certKey); - - // Add Extensions - JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils(); - certBuilder - // A BasicConstraints to mark as CA certificate and how many CA certificates - // can follow it in the chain - // (with 0 meaning the chain ends with the next certificate in the chain). - .addExtension(Extension.basicConstraints, true, new BasicConstraints(0)) - // KeyUsage specifies what the public key in the certificate can be used for. - // In this case, it can be used for signing other certificates and/or - // signing Certificate Revocation Lists (CRLs). - .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)) - .addExtension(Extension.authorityKeyIdentifier, false, - extensionUtils.createAuthorityKeyIdentifier(signerCert)) - .addExtension(Extension.subjectKeyIdentifier, false, extensionUtils.createSubjectKeyIdentifier(certKey)); - - ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER) - .build(signerKey); - - JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER); - - return converter.getCertificate(certBuilder.build(signer)); - } - - public static X509Certificate createEndEntityCertificate(X509Certificate signerCert, PrivateKey signerKey, - PublicKey certKey, String distinguishedName) throws Exception { - - X500Principal subject = new X500Principal(distinguishedName); - BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); - - X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(signerCert.getSubjectX500Principal(), - serialNum, DEFAULT_START_DATE, DEFAULT_END_DATE, subject, certKey); - - JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils(); - certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)) - .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)) - .addExtension(Extension.authorityKeyIdentifier, false, - extensionUtils.createAuthorityKeyIdentifier(signerCert)) - .addExtension(Extension.subjectKeyIdentifier, false, extensionUtils.createSubjectKeyIdentifier(certKey)); - - ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER) - .build(signerKey); - - JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER); - - return converter.getCertificate(certBuilder.build(signer)); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java deleted file mode 100644 index d15b4d561..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2020-2022 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.server.authorization.web; - -import java.util.ArrayList; -import java.util.List; - -import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.oauth2.jose.TestJwks; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link NimbusJwkSetEndpointFilter}. - * - * @author Joe Grandja - */ -public class NimbusJwkSetEndpointFilterTests { - - private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; - - private List jwkList; - - private JWKSource jwkSource; - - private NimbusJwkSetEndpointFilter filter; - - @BeforeEach - public void setUp() { - this.jwkList = new ArrayList<>(); - this.jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(new JWKSet(this.jwkList)); - this.filter = new NimbusJwkSetEndpointFilter(this.jwkSource); - } - - @Test - public void constructorWhenJwkSourceNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new NimbusJwkSetEndpointFilter(null)).isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwkSource cannot be null"); - } - - @Test - public void constructorWhenJwkSetEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new NimbusJwkSetEndpointFilter(this.jwkSource, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("jwkSetEndpointUri cannot be empty"); - } - - @Test - public void doFilterWhenNotJwkSetRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenJwkSetRequestPostThenNotProcessed() throws Exception { - String requestUri = DEFAULT_JWK_SET_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAsymmetricKeysThenJwkSetResponse() throws Exception { - RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK; - this.jwkList.add(rsaJwk); - ECKey ecJwk = TestJwks.DEFAULT_EC_JWK; - this.jwkList.add(ecJwk); - - String requestUri = DEFAULT_JWK_SET_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - - JWKSet jwkSet = JWKSet.parse(response.getContentAsString()); - assertThat(jwkSet.getKeys()).hasSize(2); - - RSAKey rsaJwkResult = (RSAKey) jwkSet.getKeyByKeyId(rsaJwk.getKeyID()); - assertThat(rsaJwkResult).isNotNull(); - assertThat(rsaJwkResult.toRSAPublicKey()).isEqualTo(rsaJwk.toRSAPublicKey()); - assertThat(rsaJwkResult.toRSAPrivateKey()).isNull(); - assertThat(rsaJwkResult.getKeyUse()).isEqualTo(KeyUse.SIGNATURE); - - ECKey ecJwkResult = (ECKey) jwkSet.getKeyByKeyId(ecJwk.getKeyID()); - assertThat(ecJwkResult).isNotNull(); - assertThat(ecJwkResult.toECPublicKey()).isEqualTo(ecJwk.toECPublicKey()); - assertThat(ecJwkResult.toECPrivateKey()).isNull(); - assertThat(ecJwkResult.getKeyUse()).isEqualTo(KeyUse.SIGNATURE); - } - - @Test - public void doFilterWhenSymmetricKeysThenJwkSetResponseEmpty() throws Exception { - OctetSequenceKey secretJwk = TestJwks.DEFAULT_SECRET_JWK; - this.jwkList.add(secretJwk); - - String requestUri = DEFAULT_JWK_SET_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - - JWKSet jwkSet = JWKSet.parse(response.getContentAsString()); - assertThat(jwkSet.getKeys()).isEmpty(); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java deleted file mode 100644 index 77327f4e8..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java +++ /dev/null @@ -1,767 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2AuthorizationEndpointFilter}. - * - * @author Paurav Munshi - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @author Anoop Garlapati - * @author Dmitriy Dubson - * @author Greg Li - * @since 0.0.1 - */ -public class OAuth2AuthorizationEndpointFilterTests { - - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize"; - - private static final String STATE = "state"; - - private static final String REMOTE_ADDRESS = "remote-address"; - - private AuthenticationManager authenticationManager; - - private OAuth2AuthorizationEndpointFilter filter; - - private TestingAuthenticationToken principal; - - private OAuth2AuthorizationCode authorizationCode; - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2AuthorizationEndpointFilter(this.authenticationManager); - this.principal = new TestingAuthenticationToken("principalName", "password"); - this.principal.setAuthenticated(true); - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(this.principal); - SecurityContextHolder.setContext(securityContext); - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); - this.authorizationCode = new OAuth2AuthorizationCode("code", issuedAt, expiresAt); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenAuthorizationEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationDetailsSource cannot be null"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void setSessionAuthenticationStrategyWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setSessionAuthenticationStrategy(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("sessionAuthenticationStrategy cannot be null"); - } - - @Test - public void doFilterWhenNotAuthorizationRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleRequestUriThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.REQUEST_URI, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.REQUEST_URI, OAuth2ParameterNames.REQUEST_URI); - request.addParameter(OAuth2ParameterNames.REQUEST_URI, "request_uri_2"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMissingResponseTypeThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.removeParameter(OAuth2ParameterNames.RESPONSE_TYPE); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleResponseTypeThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.RESPONSE_TYPE, "id_token"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestInvalidResponseTypeThenUnsupportedResponseTypeError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, (request) -> { - request.setParameter(OAuth2ParameterNames.RESPONSE_TYPE, "id_token"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMissingClientIdThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.CLIENT_ID, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.removeParameter(OAuth2ParameterNames.CLIENT_ID); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleClientIdThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.CLIENT_ID, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleRedirectUriThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.REDIRECT_URI, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.REDIRECT_URI, "https://example2.com"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleScopeThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.SCOPE, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.SCOPE, "scope2"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleStateThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - OAuth2ParameterNames.STATE, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(OAuth2ParameterNames.STATE, "state2"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationConsentRequestMissingStateThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationConsentRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.STATE, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request.removeParameter(OAuth2ParameterNames.STATE)); - } - - @Test - public void doFilterWhenAuthorizationConsentRequestMultipleStateThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationConsentRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.STATE, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.STATE, "state2")); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleCodeChallengeThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - PkceParameterNames.CODE_CHALLENGE, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - request.addParameter(PkceParameterNames.CODE_CHALLENGE, "another-code-challenge"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestMultipleCodeChallengeMethodThenInvalidRequestError() throws Exception { - doFilterWhenAuthorizationRequestInvalidParameterThenError(TestRegisteredClients.registeredClient().build(), - PkceParameterNames.CODE_CHALLENGE_METHOD, OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - request.addParameter(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthenticationRequestMultiplePromptThenInvalidRequestError() throws Exception { - // Setup OpenID Connect request - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(OidcScopes.OPENID); - }).build(); - doFilterWhenAuthorizationRequestInvalidParameterThenError(registeredClient, "prompt", - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter("prompt", "none"); - request.addParameter("prompt", "login"); - updateQueryString(request); - }); - } - - @Test - public void doFilterWhenAuthorizationRequestAuthenticationExceptionThenErrorResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://example.com?param=encoded%20parameter%20value"); - }).build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, - registeredClient.getRedirectUris().iterator().next(), "client state", registeredClient.getScopes(), - null); - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "error description", "error uri"); - given(this.authenticationManager.authenticate(any())) - .willThrow(new OAuth2AuthorizationCodeRequestAuthenticationException(error, - authorizationCodeRequestAuthentication)); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo( - "https://example.com?param=encoded%20parameter%20value&error=invalid_request&error_description=error%20description&error_uri=error%20uri&state=client%20state"); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.principal); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - verify(this.authenticationManager).authenticate(any()); - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), - same(authorizationCodeRequestAuthenticationResult)); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); - OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri"); - OAuth2AuthorizationCodeRequestAuthenticationException authenticationException = new OAuth2AuthorizationCodeRequestAuthenticationException( - error, authorizationCodeRequestAuthentication); - given(this.authenticationManager.authenticate(any())).willThrow(authenticationException); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), same(authenticationException)); - } - - @Test - public void doFilterWhenCustomSessionAuthenticationStrategyThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class); - this.filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - verify(sessionAuthenticationStrategy).onAuthentication(same(authorizationCodeRequestAuthenticationResult), - any(), any()); - } - - @Test - public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request); - given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationDetailsSource).buildDetails(any()); - verify(this.authenticationManager).authenticate(any()); - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception { - this.principal.setAuthenticated(false); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); - authorizationCodeRequestAuthenticationResult.setAuthenticated(false); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationRequestConsentRequiredWithCustomConsentUriThenRedirectConsentResponse() - throws Exception { - Set requestedScopes = new HashSet<>(Arrays.asList("scope1", "scope2")); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.addAll(requestedScopes); - }).build(); - // No scopes previously approved - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, new HashSet<>(), null); - authorizationConsentAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationConsentAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.setConsentPage("/oauth2/custom-consent"); - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()) - .isEqualTo("http://localhost/oauth2/custom-consent?scope=scope1%20scope2&client_id=client-1&state=state"); - } - - @Test - public void doFilterWhenAuthorizationRequestConsentRequiredThenConsentResponse() throws Exception { - Set requestedScopes = new HashSet<>(Arrays.asList("scope1", "scope2")); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.addAll(requestedScopes); - }).build(); - // No scopes previously approved - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, new HashSet<>(), null); - authorizationConsentAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationConsentAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentType().equals(new MediaType("text", "html", StandardCharsets.UTF_8).toString())); - for (String requestedScope : requestedScopes) { - assertThat(response.getContentAsString()).contains(scopeCheckbox(requestedScope)); - } - } - - @Test - public void doFilterWhenAuthorizationRequestConsentRequiredWithPreviouslyApprovedThenConsentResponse() - throws Exception { - Set approvedScopes = new HashSet<>(Arrays.asList("scope1", "scope2")); - Set requestedScopes = new HashSet<>(Arrays.asList("scope3", "scope4")); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.addAll(approvedScopes); - scopes.addAll(requestedScopes); - }).build(); - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult = new OAuth2AuthorizationConsentAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, STATE, approvedScopes, null); - authorizationConsentAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationConsentAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentType().equals(new MediaType("text", "html", StandardCharsets.UTF_8).toString())); - for (String requestedScope : requestedScopes) { - assertThat(response.getContentAsString()).contains(scopeCheckbox(requestedScope)); - } - for (String approvedScope : approvedScopes) { - assertThat(response.getContentAsString()).contains(disabledScopeCheckbox(approvedScope)); - } - } - - @Test - public void doFilterWhenAuthorizationRequestAuthenticatedThenAuthorizationResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://example.com?param=encoded%20parameter%20value"); - }).build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode, - registeredClient.getRedirectUris().iterator().next(), "client state", registeredClient.getScopes()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - request.addParameter("custom-param", "custom-value-1", "custom-value-2"); - updateQueryString(request); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - ArgumentCaptor authorizationCodeRequestAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationCodeRequestAuthenticationToken.class); - verify(this.authenticationManager).authenticate(authorizationCodeRequestAuthenticationCaptor.capture()); - verifyNoInteractions(filterChain); - - assertThat(authorizationCodeRequestAuthenticationCaptor.getValue().getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - // Assert that multi-valued request parameters are preserved - assertThat(authorizationCodeRequestAuthenticationCaptor.getValue().getAdditionalParameters()) - .extracting((params) -> params.get("custom-param")) - .asInstanceOf(InstanceOfAssertFactories.type(String[].class)) - .isEqualTo(new String[] { "custom-value-1", "custom-value-2" }); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()) - .isEqualTo("https://example.com?param=encoded%20parameter%20value&code=code&state=client%20state"); - } - - @Test - public void doFilterWhenPostAuthorizationRequestAuthenticatedThenAuthorizationResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://example.com?param=encoded%20parameter%20value"); - }).build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode, - registeredClient.getRedirectUris().iterator().next(), "client state", registeredClient.getScopes()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - request.setMethod("POST"); - request.setQueryString(null); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()) - .isEqualTo("https://example.com?param=encoded%20parameter%20value&code=code&state=client%20state"); - } - - @Test - public void doFilterWhenAuthenticationRequestAuthenticatedThenAuthorizationResponse() throws Exception { - // Setup OpenID Connect request - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(OidcScopes.OPENID); - }).build(); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult); - - MockHttpServletRequest request = createAuthorizationRequest(registeredClient); - request.setMethod("POST"); // OpenID Connect supports POST method - request.setQueryString(null); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()) - .isEqualTo(request.getParameter(OAuth2ParameterNames.REDIRECT_URI) + "?code=code&state=state"); - } - - private void doFilterWhenAuthorizationRequestInvalidParameterThenError(RegisteredClient registeredClient, - String parameterName, String errorCode, Consumer requestConsumer) throws Exception { - - doFilterWhenRequestInvalidParameterThenError(createAuthorizationRequest(registeredClient), parameterName, - errorCode, requestConsumer); - } - - private void doFilterWhenAuthorizationConsentRequestInvalidParameterThenError(RegisteredClient registeredClient, - String parameterName, String errorCode, Consumer requestConsumer) throws Exception { - - doFilterWhenRequestInvalidParameterThenError(createAuthorizationConsentRequest(registeredClient), parameterName, - errorCode, requestConsumer); - } - - private void doFilterWhenRequestInvalidParameterThenError(MockHttpServletRequest request, String parameterName, - String errorCode, Consumer requestConsumer) throws Exception { - - requestConsumer.accept(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getErrorMessage()).isEqualTo("[" + errorCode + "] OAuth 2.0 Parameter: " + parameterName); - } - - private static MockHttpServletRequest createAuthorizationRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_AUTHORIZATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - request.addParameter(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - request.addParameter(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - updateQueryString(request); - - return request; - } - - private static MockHttpServletRequest createAuthorizationConsentRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_AUTHORIZATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - registeredClient.getScopes().forEach((scope) -> request.addParameter(OAuth2ParameterNames.SCOPE, scope)); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - - return request; - } - - private static void updateQueryString(MockHttpServletRequest request) { - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(request.getRequestURI()); - request.getParameterMap().forEach((key, values) -> { - if (values.length > 0) { - for (String value : values) { - uriBuilder.queryParam(key, value); - } - } - }); - request.setQueryString(uriBuilder.build().getQuery()); - } - - private static String scopeCheckbox(String scope) { - return MessageFormat.format( - "", scope); - } - - private static String disabledScopeCheckbox(String scope) { - return MessageFormat.format( - "", - scope); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java deleted file mode 100644 index 34ee43071..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.web.util.InvalidUrlException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2AuthorizationServerMetadataEndpointFilter}. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - */ -public class OAuth2AuthorizationServerMetadataEndpointFilterTests { - - private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; - - private final OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); - - @AfterEach - public void cleanup() { - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void setAuthorizationServerMetadataCustomizerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthorizationServerMetadataCustomizer(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authorizationServerMetadataCustomizer cannot be null"); - } - - @Test - public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception { - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationServerMetadataRequestPostThenNotProcessed() throws Exception { - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - - String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationServerMetadataRequestThenMetadataResponse() throws Exception { - String issuer = "https://example.com"; - String authorizationEndpoint = "/oauth2/v1/authorize"; - String pushedAuthorizationRequestEndpoint = "/oauth2/v1/par"; - String tokenEndpoint = "/oauth2/v1/token"; - String jwkSetEndpoint = "/oauth2/v1/jwks"; - String tokenRevocationEndpoint = "/oauth2/v1/revoke"; - String tokenIntrospectionEndpoint = "/oauth2/v1/introspect"; - - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer(issuer) - .authorizationEndpoint(authorizationEndpoint) - .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint) - .tokenEndpoint(tokenEndpoint) - .jwkSetEndpoint(jwkSetEndpoint) - .tokenRevocationEndpoint(tokenRevocationEndpoint) - .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint) - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - - String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); - String authorizationServerMetadataResponse = response.getContentAsString(); - assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"pushed_authorization_request_endpoint\":\"https://example.com/oauth2/v1/par\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\""); - assertThat(authorizationServerMetadataResponse).contains( - "\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\""); - assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]"); - assertThat(authorizationServerMetadataResponse).contains( - "\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]"); - assertThat(authorizationServerMetadataResponse) - .contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\""); - assertThat(authorizationServerMetadataResponse).contains( - "\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - assertThat(authorizationServerMetadataResponse) - .contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\""); - assertThat(authorizationServerMetadataResponse).contains( - "\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"S256\"]"); - assertThat(authorizationServerMetadataResponse).contains("\"tls_client_certificate_bound_access_tokens\":true"); - assertThat(authorizationServerMetadataResponse).contains( - "\"dpop_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\",\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES384\",\"ES512\"]"); - } - - @Test - public void doFilterWhenAuthorizationServerSettingsWithInvalidIssuerThenThrowIllegalArgumentException() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://this is an invalid URL") - .build(); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - - String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - assertThatThrownBy(() -> this.filter.doFilter(request, response, filterChain)) - .isInstanceOf(InvalidUrlException.class); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java deleted file mode 100644 index f964b094e..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.nio.charset.StandardCharsets; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.codec.Hex; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2ClientAuthenticationFilter}. - * - * @author Patryk Kostrzewa - * @author Joe Grandja - */ -public class OAuth2ClientAuthenticationFilterTests { - - private String filterProcessesUrl = "/oauth2/token"; - - private AuthenticationManager authenticationManager; - - private RequestMatcher requestMatcher; - - private AuthenticationConverter authenticationConverter; - - private OAuth2ClientAuthenticationFilter filter; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.requestMatcher = PathPatternRequestMatcher.withDefaults() - .matcher(HttpMethod.POST, this.filterProcessesUrl); - this.filter = new OAuth2ClientAuthenticationFilter(this.authenticationManager, this.requestMatcher); - this.authenticationConverter = mock(AuthenticationConverter.class); - this.filter.setAuthenticationConverter(this.authenticationConverter); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(null, this.requestMatcher)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenRequestMatcherNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("requestMatcher cannot be null"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenRequestDoesNotMatchThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - verifyNoInteractions(this.authenticationConverter); - } - - @Test - public void doFilterWhenRequestMatchesAndEmptyCredentialsThenNotProcessed() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl); - request.setServletPath(this.filterProcessesUrl); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - verifyNoInteractions(this.authenticationManager); - } - - @Test - public void doFilterWhenRequestMatchesAndInvalidCredentialsThenInvalidRequestError() throws Exception { - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willThrow(new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST)); - - MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl); - request.setServletPath(this.filterProcessesUrl); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - // gh-889 - @Test - public void doFilterWhenRequestMatchesAndClientIdContainsNonPrintableASCIIThenInvalidRequestError() - throws Exception { - // Hex 00 -> null - String clientId = new String(Hex.decode("00"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenInvalidRequestError(clientId); - - // Hex 0a61 -> line feed + a - clientId = new String(Hex.decode("0a61"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenInvalidRequestError(clientId); - - // Hex 1b -> escape - clientId = new String(Hex.decode("1b"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenInvalidRequestError(clientId); - - // Hex 1b61 -> escape + a - clientId = new String(Hex.decode("1b61"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenInvalidRequestError(clientId); - } - - private void assertWhenInvalidClientIdThenInvalidRequestError(String clientId) throws Exception { - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - "secret", null)); - - MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl); - request.setServletPath(this.filterProcessesUrl); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verifyNoInteractions(this.authenticationManager); - - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError() throws Exception { - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new OAuth2ClientAuthenticationToken("clientId", ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - "invalid-secret", null)); - given(this.authenticationManager.authenticate(any(Authentication.class))) - .willThrow(new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT)); - - MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl); - request.setServletPath(this.filterProcessesUrl); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verify(this.authenticationManager).authenticate(any()); - - assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); - } - - @Test - public void doFilterWhenRequestMatchesAndValidCredentialsThenProcessed() throws Exception { - final String remoteAddress = "remote-address"; - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null)); - given(this.authenticationManager.authenticate(any(Authentication.class))) - .willReturn(new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret())); - - MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl); - request.setServletPath(this.filterProcessesUrl); - request.setRemoteAddr(remoteAddress); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - assertThat(authentication).isInstanceOf(OAuth2ClientAuthenticationToken.class); - assertThat(((OAuth2ClientAuthenticationToken) authentication).getRegisteredClient()) - .isEqualTo(registeredClient); - - ArgumentCaptor authenticationRequestCaptor = ArgumentCaptor - .forClass(OAuth2ClientAuthenticationToken.class); - verify(this.authenticationManager).authenticate(authenticationRequestCaptor.capture()); - assertThat(authenticationRequestCaptor).extracting(ArgumentCaptor::getValue) - .extracting(OAuth2ClientAuthenticationToken::getDetails) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(remoteAddress); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java deleted file mode 100644 index 19b8ce875..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.io.IOException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Map; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; - -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; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2DeviceAuthorizationEndpointFilter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceAuthorizationEndpointFilterTests { - - private static final String ISSUER_URI = "https://provider.com:8090"; - - private static final String REMOTE_ADDRESS = "remote-address"; - - private static final String AUTHORIZATION_URI = "/oauth2/device_authorization"; - - private static final String VERIFICATION_URI = "/oauth2/device_verification"; - - private static final String CLIENT_ID = "client-1"; - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private AuthenticationManager authenticationManager; - - private OAuth2DeviceAuthorizationEndpointFilter filter; - - private final HttpMessageConverter deviceAuthorizationHttpResponseConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2DeviceAuthorizationEndpointFilter(this.authenticationManager); - mockAuthorizationServerContext(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthenticationMangerIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationEndpointFilter(null)) - .withMessage("authenticationManager cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenDeviceAuthorizationEndpointUriIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceAuthorizationEndpointFilter(this.authenticationManager, null)) - .withMessage("deviceAuthorizationEndpointUri cannot be empty"); - // @formatter:on - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .withMessage("authenticationConverter cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .withMessage("authenticationDetailsSource cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .withMessage("authenticationSuccessHandler cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .withMessage("authenticationFailureHandler cannot be null"); - // @formatter:on - } - - @Test - public void setVerificationUriWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setVerificationUri(null)) - .withMessage("verificationUri cannot be empty"); - // @formatter:on - } - - @Test - public void doFilterWhenNotDeviceAuthorizationRequestThenNotProcessed() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/path"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - verify(filterChain).doFilter(request, response); - verifyNoInteractions(this.authenticationManager); - } - - @Test - public void doFilterWhenDeviceAuthorizationRequestGetThenNotProcessed() throws Exception { - MockHttpServletRequest request = createRequest(); - request.setMethod(HttpMethod.GET.name()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - verify(filterChain).doFilter(request, response); - verifyNoInteractions(this.authenticationManager); - } - - @Test - public void doFilterWhenDeviceAuthorizationRequestThenDeviceAuthorizationResponse() throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - ArgumentCaptor deviceAuthorizationRequestAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2DeviceAuthorizationRequestAuthenticationToken.class); - verify(this.authenticationManager).authenticate(deviceAuthorizationRequestAuthenticationCaptor.capture()); - verifyNoInteractions(filterChain); - - OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthentication = deviceAuthorizationRequestAuthenticationCaptor - .getValue(); - assertThat(deviceAuthorizationRequestAuthentication.getAuthorizationUri()).endsWith(AUTHORIZATION_URI); - assertThat(deviceAuthorizationRequestAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(deviceAuthorizationRequestAuthentication.getScopes()).isEmpty(); - assertThat(deviceAuthorizationRequestAuthentication.getAdditionalParameters()).containsExactly( - Map.entry("custom-param-1", "custom-value-1"), - Map.entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - // @formatter:off - assertThat(deviceAuthorizationRequestAuthentication.getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - // @formatter:on - - OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = readDeviceAuthorizationResponse(response); - String verificationUri = ISSUER_URI + VERIFICATION_URI; - assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo(verificationUri); - assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) - .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); - OAuth2DeviceCode deviceCode = deviceAuthorizationResponse.getDeviceCode(); - assertThat(deviceCode.getTokenValue()).isEqualTo(DEVICE_CODE); - assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); - OAuth2UserCode userCode = deviceAuthorizationResponse.getUserCode(); - assertThat(userCode.getTokenValue()).isEqualTo(USER_CODE); - assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); - } - - // gh-1714 - @Test - public void doFilterWhenDeviceAuthorizationRequestWithContextPathThenVerificationUriIncludesContextPath() - throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - request.setContextPath("/contextPath"); - request.setRequestURI("/contextPath" + AUTHORIZATION_URI); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(OAuth2DeviceAuthorizationRequestAuthenticationToken.class)); - verifyNoInteractions(filterChain); - - OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = readDeviceAuthorizationResponse(response); - String verificationUri = ISSUER_URI + "/contextPath" + VERIFICATION_URI; - assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo(verificationUri); - assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) - .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); - } - - @Test - public void doFilterWhenInvalidRequestErrorThenBadRequest() throws Exception { - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - OAuth2AuthenticationException authenticationException = new OAuth2AuthenticationException( - new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "Invalid request", "error-uri")); - given(authenticationConverter.convert(any(HttpServletRequest.class))).willThrow(authenticationException); - this.filter.setAuthenticationConverter(authenticationConverter); - - MockHttpServletRequest request = createRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - - verify(authenticationConverter).convert(request); - verifyNoInteractions(filterChain, this.authenticationManager); - - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - assertThat(error.getDescription()).isEqualTo("Invalid request"); - assertThat(error.getUri()).isEqualTo("error-uri"); - } - - @Test - public void doFilterWhenCustomDeviceAuthorizationEndpointUriThenUsed() throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - request.setRequestURI("/device"); - request.setServletPath("/device"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter = new OAuth2DeviceAuthorizationEndpointFilter(this.authenticationManager, "/device"); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationConverterSetThenUsed() throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - OAuth2DeviceAuthorizationRequestAuthenticationToken authenticationRequest = new OAuth2DeviceAuthorizationRequestAuthenticationToken( - clientPrincipal, AUTHORIZATION_URI, null, null); - given(authenticationConverter.convert(any(HttpServletRequest.class))).willReturn(authenticationRequest); - this.filter.setAuthenticationConverter(authenticationConverter); - - MockHttpServletRequest request = createRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(authenticationConverter).convert(request); - verify(this.authenticationManager).authenticate(authenticationRequest); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationDetailsSourceSetThenUsed() throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - @SuppressWarnings("unchecked") - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - given(authenticationDetailsSource.buildDetails(any(HttpServletRequest.class))) - .willReturn(new WebAuthenticationDetails(request)); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationDetailsSource).buildDetails(request); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationSuccessHandlerSetThenUsed() throws Exception { - Authentication authenticationResult = createAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - MockHttpServletRequest request = createRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationSuccessHandler).onAuthenticationSuccess(request, response, authenticationResult); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationFailureHandlerSetThenUsed() throws Exception { - OAuth2AuthenticationException authenticationException = new OAuth2AuthenticationException( - OAuth2ErrorCodes.INVALID_REQUEST); - given(this.authenticationManager.authenticate(any(Authentication.class))).willThrow(authenticationException); - - Authentication clientPrincipal = (Authentication) createAuthentication().getPrincipal(); - mockSecurityContext(clientPrincipal); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - MockHttpServletRequest request = createRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationFailureHandler).onAuthenticationFailure(request, response, authenticationException); - verifyNoInteractions(filterChain); - } - - private OAuth2DeviceAuthorizationResponse readDeviceAuthorizationResponse(MockHttpServletResponse response) - throws IOException { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.deviceAuthorizationHttpResponseConverter.read(OAuth2DeviceAuthorizationResponse.class, - httpResponse); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws IOException { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> ISSUER_URI); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static void mockSecurityContext(Authentication clientPrincipal) { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.POST.name()); - request.setRequestURI(AUTHORIZATION_URI); - request.setServletPath(AUTHORIZATION_URI); - request.setRemoteAddr(REMOTE_ADDRESS); - request.setScheme("https"); - request.setServerName("provider.com"); - request.setServerPort(8090); - return request; - } - - private static OAuth2DeviceAuthorizationRequestAuthenticationToken createAuthentication() { - TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(CLIENT_ID, null); - return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, null, createDeviceCode(), - createUserCode()); - } - - private static OAuth2DeviceCode createDeviceCode() { - Instant issuedAt = Instant.now(); - return new OAuth2DeviceCode(DEVICE_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - - private static OAuth2UserCode createUserCode() { - Instant issuedAt = Instant.now(); - return new OAuth2UserCode(USER_CODE, issuedAt, issuedAt.plus(30, ChronoUnit.MINUTES)); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilterTests.java deleted file mode 100644 index f755e75c3..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilterTests.java +++ /dev/null @@ -1,492 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web; - -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.web.util.UriComponentsBuilder; - -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; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2DeviceVerificationEndpointFilter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceVerificationEndpointFilterTests { - - private static final String ISSUER_URI = "https://provider.com"; - - private static final String REMOTE_ADDRESS = "remote-address"; - - private static final String AUTHORIZATION_URI = "/oauth2/device_authorization"; - - private static final String VERIFICATION_URI = "/oauth2/device_verification"; - - private static final String CLIENT_ID = "client-1"; - - private static final String STATE = "12345"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private AuthenticationManager authenticationManager; - - private OAuth2DeviceVerificationEndpointFilter filter; - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2DeviceVerificationEndpointFilter(this.authenticationManager); - mockAuthorizationServerContext(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthenticationManagerIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceVerificationEndpointFilter(null)) - .withMessage("authenticationManager cannot be null"); - // @formatter:on - } - - @Test - public void constructorWhenDeviceVerificationEndpointUriIsNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> new OAuth2DeviceVerificationEndpointFilter(this.authenticationManager, null)) - .withMessage("deviceVerificationEndpointUri cannot be empty"); - // @formatter:on - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .withMessage("authenticationConverter cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .withMessage("authenticationDetailsSource cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .withMessage("authenticationSuccessHandler cannot be null"); - // @formatter:on - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .withMessage("authenticationFailureHandler cannot be null"); - // @formatter:on - } - - @Test - public void doFilterWhenNotDeviceVerificationRequestThenNotProcessed() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/path"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - verify(filterChain).doFilter(request, response); - verifyNoInteractions(this.authenticationManager); - } - - @Test - public void doFilterWhenUnauthenticatedThenPassThrough() throws Exception { - TestingAuthenticationToken unauthenticatedResult = new TestingAuthenticationToken("user", null); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(unauthenticatedResult); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(filterChain).doFilter(request, response); - } - - @Test - public void doFilterWhenDeviceAuthorizationConsentRequestThenSuccess() throws Exception { - Authentication authenticationResult = createDeviceVerificationAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - request.setMethod(HttpMethod.POST.name()); - request.addParameter(OAuth2ParameterNames.SCOPE, "scope-1"); - request.addParameter(OAuth2ParameterNames.SCOPE, "scope-2"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - ArgumentCaptor authenticationCaptor = ArgumentCaptor - .forClass(OAuth2DeviceAuthorizationConsentAuthenticationToken.class); - verify(this.authenticationManager).authenticate(authenticationCaptor.capture()); - verifyNoInteractions(filterChain); - - OAuth2DeviceAuthorizationConsentAuthenticationToken deviceAuthorizationConsentAuthentication = authenticationCaptor - .getValue(); - assertThat(deviceAuthorizationConsentAuthentication.getAuthorizationUri()).endsWith(VERIFICATION_URI); - assertThat(deviceAuthorizationConsentAuthentication.getClientId()).isEqualTo(CLIENT_ID); - assertThat(deviceAuthorizationConsentAuthentication.getPrincipal()) - .isInstanceOf(TestingAuthenticationToken.class); - assertThat(deviceAuthorizationConsentAuthentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(deviceAuthorizationConsentAuthentication.getScopes()).containsExactly("scope-1", "scope-2"); - assertThat(deviceAuthorizationConsentAuthentication.getAdditionalParameters()).containsExactly( - Map.entry("custom-param-1", "custom-value-1"), - Map.entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - } - - @Test - public void doFilterWhenDeviceVerificationRequestAndConsentNotRequiredThenSuccess() throws Exception { - Authentication authenticationResult = createDeviceVerificationAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); - mockSecurityContext(clientPrincipal); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter("custom-param-1", "custom-value-1"); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - ArgumentCaptor authenticationCaptor = ArgumentCaptor - .forClass(OAuth2DeviceVerificationAuthenticationToken.class); - verify(this.authenticationManager).authenticate(authenticationCaptor.capture()); - verifyNoInteractions(filterChain); - - OAuth2DeviceVerificationAuthenticationToken deviceVerificationAuthentication = authenticationCaptor.getValue(); - assertThat(deviceVerificationAuthentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(deviceVerificationAuthentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(deviceVerificationAuthentication.getAdditionalParameters()) - .containsExactly(Map.entry("custom-param-1", "custom-value-1")); - } - - @Test - public void doFilterWhenDeviceVerificationRequestAndConsentRequiredThenConsentScreen() throws Exception { - Authentication authenticationResult = createDeviceAuthorizationConsentAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentType()) - .isEqualTo(new MediaType("text", "html", StandardCharsets.UTF_8).toString()); - assertThat(response.getContentAsString()).contains(scopeCheckbox("scope-1")); - assertThat(response.getContentAsString()).contains(scopeCheckbox("scope-2")); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenDeviceVerificationRequestAndConsentRequiredWithPreviouslyApprovedThenConsentScreen() - throws Exception { - Authentication authenticationResult = createDeviceAuthorizationConsentAuthenticationWithAuthorizedScopes(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentType()) - .isEqualTo(new MediaType("text", "html", StandardCharsets.UTF_8).toString()); - assertThat(response.getContentAsString()).contains(disabledScopeCheckbox("scope-1")); - assertThat(response.getContentAsString()).contains(scopeCheckbox("scope-2")); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenDeviceVerificationRequestAndConsentRequiredAndConsentPageSetThenRedirect() - throws Exception { - Authentication authentication = createDeviceAuthorizationConsentAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authentication); - - MockHttpServletRequest request = createRequest(); - request.setScheme("https"); - request.setServerPort(443); - request.setServerName("provider.com"); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.setConsentPage("/consent"); - this.filter.doFilter(request, response, filterChain); - String redirectUri = UriComponentsBuilder.fromUriString("https://provider.com/consent") - .queryParam(OAuth2ParameterNames.SCOPE, "scope-1 scope-2") - .queryParam(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID) - .queryParam(OAuth2ParameterNames.STATE, STATE) - .queryParam(OAuth2ParameterNames.USER_CODE, USER_CODE) - .toUriString(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader(HttpHeaders.LOCATION)).isEqualTo(redirectUri); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationConverterSetThenUsed() throws Exception { - Authentication authenticationResult = createDeviceVerificationAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - OAuth2DeviceVerificationAuthenticationToken deviceVerificationAuthentication = new OAuth2DeviceVerificationAuthenticationToken( - (Authentication) authenticationResult.getPrincipal(), USER_CODE, Collections.emptyMap()); - given(authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(deviceVerificationAuthentication); - this.filter.setAuthenticationConverter(authenticationConverter); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - verify(authenticationConverter).convert(request); - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationDetailsSourceSetThenUsed() throws Exception { - Authentication authenticationResult = createDeviceVerificationAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - @SuppressWarnings("unchecked") - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - given(authenticationDetailsSource.buildDetails(any(HttpServletRequest.class))) - .willReturn(new WebAuthenticationDetails(request)); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationDetailsSource).buildDetails(request); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationSuccessHandlerSetThenUsed() throws Exception { - Authentication authenticationResult = createDeviceVerificationAuthentication(); - given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationSuccessHandler).onAuthenticationSuccess(request, response, authenticationResult); - verifyNoInteractions(filterChain); - } - - @Test - public void doFilterWhenAuthenticationFailureHandlerSetThenUsed() throws Exception { - OAuth2AuthenticationException authenticationException = new OAuth2AuthenticationException( - OAuth2ErrorCodes.INVALID_REQUEST); - given(this.authenticationManager.authenticate(any(Authentication.class))).willThrow(authenticationException); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - updateQueryString(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - this.filter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - - verify(this.authenticationManager).authenticate(any(Authentication.class)); - verify(authenticationFailureHandler).onAuthenticationFailure(request, response, authenticationException); - verifyNoInteractions(filterChain); - } - - private static void mockAuthorizationServerContext() { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext( - authorizationServerSettings, () -> ISSUER_URI); - AuthorizationServerContextHolder.setContext(authorizationServerContext); - } - - private static void mockSecurityContext(Authentication clientPrincipal) { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - } - - private static OAuth2DeviceVerificationAuthenticationToken createDeviceVerificationAuthentication() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null); - return new OAuth2DeviceVerificationAuthenticationToken(principal, CLIENT_ID, USER_CODE); - } - - private static Authentication createDeviceAuthorizationConsentAuthentication() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null); - Set requestedScopes = new HashSet<>(); - requestedScopes.add("scope-1"); - requestedScopes.add("scope-2"); - return new OAuth2DeviceAuthorizationConsentAuthenticationToken(AUTHORIZATION_URI, CLIENT_ID, principal, - USER_CODE, STATE, requestedScopes, new HashSet<>()); - } - - private static Authentication createDeviceAuthorizationConsentAuthenticationWithAuthorizedScopes() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("user", null); - Set requestedScopes = new HashSet<>(); - requestedScopes.add("scope-1"); - requestedScopes.add("scope-2"); - Set authorizedScopes = new HashSet<>(); - authorizedScopes.add("scope-1"); - return new OAuth2DeviceAuthorizationConsentAuthenticationToken(AUTHORIZATION_URI, CLIENT_ID, principal, - USER_CODE, STATE, requestedScopes, authorizedScopes); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.GET.name()); - request.setRequestURI(VERIFICATION_URI); - request.setServletPath(VERIFICATION_URI); - request.setRemoteAddr(REMOTE_ADDRESS); - return request; - } - - private static void updateQueryString(MockHttpServletRequest request) { - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(request.getRequestURI()); - request.getParameterMap().forEach((key, values) -> { - if (values.length > 0) { - for (String value : values) { - uriBuilder.queryParam(key, value); - } - } - }); - request.setQueryString(uriBuilder.build().getQuery()); - } - - private static String scopeCheckbox(String scope) { - return MessageFormat.format( - "", scope); - } - - private static String disabledScopeCheckbox(String scope) { - return MessageFormat.format( - "", - scope); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java deleted file mode 100644 index 772eb0726..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.time.Instant; -import java.util.Map; -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2PushedAuthorizationRequestEndpointFilter}. - * - * @author Joe Grandja - */ -public class OAuth2PushedAuthorizationRequestEndpointFilterTests { - - private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/par"; - - private static final String STATE = "state"; - - private static final String REMOTE_ADDRESS = "remote-address"; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); - - private AuthenticationManager authenticationManager; - - private OAuth2PushedAuthorizationRequestEndpointFilter filter; - - private TestingAuthenticationToken clientPrincipal; - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2PushedAuthorizationRequestEndpointFilter(this.authenticationManager); - this.clientPrincipal = new TestingAuthenticationToken("client-id", "client-secret"); - this.clientPrincipal.setAuthenticated(true); - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(this.clientPrincipal); - SecurityContextHolder.setContext(securityContext); - AuthorizationServerContextHolder - .setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - AuthorizationServerContextHolder.resetContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2PushedAuthorizationRequestEndpointFilter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenPushedAuthorizationRequestEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2PushedAuthorizationRequestEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("pushedAuthorizationRequestEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationDetailsSource cannot be null"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotPushedAuthorizationRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestIncludesRequestUriThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.REQUEST_URI, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.REQUEST_URI, OAuth2ParameterNames.REQUEST_URI)); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleResponseTypeThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.RESPONSE_TYPE, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.RESPONSE_TYPE, "id_token")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestInvalidResponseTypeThenUnsupportedResponseTypeError() - throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.RESPONSE_TYPE, - OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, - (request) -> request.setParameter(OAuth2ParameterNames.RESPONSE_TYPE, "id_token")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMissingClientIdThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.CLIENT_ID, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request.removeParameter(OAuth2ParameterNames.CLIENT_ID)); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleClientIdThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.CLIENT_ID, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleRedirectUriThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.REDIRECT_URI, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.REDIRECT_URI, "https://example2.com")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleScopeThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.SCOPE, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.SCOPE, "scope2")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleStateThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), OAuth2ParameterNames.STATE, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.STATE, "state2")); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleCodeChallengeThenInvalidRequestError() throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), PkceParameterNames.CODE_CHALLENGE, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); - request.addParameter(PkceParameterNames.CODE_CHALLENGE, "another-code-challenge"); - }); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestMultipleCodeChallengeMethodThenInvalidRequestError() - throws Exception { - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError( - TestRegisteredClients.registeredClient().build(), PkceParameterNames.CODE_CHALLENGE_METHOD, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - request.addParameter(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - }); - } - - @Test - public void doFilterWhenPushedAuthenticationRequestMultiplePromptThenInvalidRequestError() throws Exception { - // Setup OpenID Connect request - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add(OidcScopes.OPENID); - }).build(); - doFilterWhenPushedAuthorizationRequestInvalidParameterThenError(registeredClient, "prompt", - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> { - request.addParameter("prompt", "none"); - request.addParameter("prompt", "login"); - }); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestAuthenticationExceptionThenErrorResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "error description", "error uri"); - given(this.authenticationManager.authenticate(any())).willThrow(new OAuth2AuthenticationException(error)); - - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error errorResponse = readError(response); - assertThat(errorResponse.getErrorCode()).isEqualTo(error.getErrorCode()); - assertThat(errorResponse.getDescription()).isEqualTo(error.getDescription()); - assertThat(errorResponse.getUri()).isEqualTo(error.getUri()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.clientPrincipal); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthenticationResult = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.clientPrincipal, - OAuth2ParameterNames.REQUEST_URI, Instant.now().plusSeconds(30), - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn(pushedAuthorizationRequestAuthenticationResult); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(this.authenticationManager.authenticate(any())) - .willReturn(pushedAuthorizationRequestAuthenticationResult); - - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - verify(this.authenticationManager).authenticate(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthenticationResult = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.clientPrincipal, - OAuth2ParameterNames.REQUEST_URI, Instant.now().plusSeconds(30), - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - given(this.authenticationManager.authenticate(any())) - .willReturn(pushedAuthorizationRequestAuthenticationResult); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), - same(pushedAuthorizationRequestAuthenticationResult)); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri"); - OAuth2AuthenticationException authenticationException = new OAuth2AuthenticationException(error); - given(this.authenticationManager.authenticate(any())).willThrow(authenticationException); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(this.authenticationManager).authenticate(any()); - verifyNoInteractions(filterChain); - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), same(authenticationException)); - } - - @Test - public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request); - given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthenticationResult = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.clientPrincipal, - OAuth2ParameterNames.REQUEST_URI, Instant.now().plusSeconds(30), - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes()); - - given(this.authenticationManager.authenticate(any())) - .willReturn(pushedAuthorizationRequestAuthenticationResult); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationDetailsSource).buildDetails(any()); - verify(this.authenticationManager).authenticate(any()); - } - - @Test - public void doFilterWhenPushedAuthorizationRequestAuthenticatedThenPushedAuthorizationResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - String requestUri = OAuth2ParameterNames.REQUEST_URI; - Instant requestUriExpiresAt = Instant.now().plusSeconds(30); - OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthenticationResult = new OAuth2PushedAuthorizationRequestAuthenticationToken( - AUTHORIZATION_URI, registeredClient.getClientId(), this.clientPrincipal, requestUri, - requestUriExpiresAt, registeredClient.getRedirectUris().iterator().next(), STATE, - registeredClient.getScopes()); - given(this.authenticationManager.authenticate(any())) - .willReturn(pushedAuthorizationRequestAuthenticationResult); - - MockHttpServletRequest request = createPushedAuthorizationRequest(registeredClient); - request.addParameter("custom-param", "custom-value-1", "custom-value-2"); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - ArgumentCaptor pushedAuthorizationRequestAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2PushedAuthorizationRequestAuthenticationToken.class); - verify(this.authenticationManager).authenticate(pushedAuthorizationRequestAuthenticationCaptor.capture()); - verifyNoInteractions(filterChain); - - assertThat(pushedAuthorizationRequestAuthenticationCaptor.getValue().getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - // Assert that multi-valued request parameters are preserved - assertThat(pushedAuthorizationRequestAuthenticationCaptor.getValue().getAdditionalParameters()) - .extracting((params) -> params.get("custom-param")) - .asInstanceOf(InstanceOfAssertFactories.type(String[].class)) - .isEqualTo(new String[] { "custom-value-1", "custom-value-2" }); - assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value()); - Map responseParameters = readPushedAuthorizationResponse(response); - assertThat(responseParameters.get(OAuth2ParameterNames.REQUEST_URI)).isEqualTo(requestUri); - Instant requestUriExpiry = Instant.now() - .plusSeconds(Long.parseLong(String.valueOf(responseParameters.get("expires_in")))); - assertThat(requestUriExpiry).isBetween(requestUriExpiresAt.minusSeconds(1), requestUriExpiresAt.plusSeconds(1)); - } - - private void doFilterWhenPushedAuthorizationRequestInvalidParameterThenError(RegisteredClient registeredClient, - String parameterName, String errorCode, Consumer requestConsumer) throws Exception { - - doFilterWhenRequestInvalidParameterThenError(createPushedAuthorizationRequest(registeredClient), parameterName, - errorCode, requestConsumer); - } - - private void doFilterWhenRequestInvalidParameterThenError(MockHttpServletRequest request, String parameterName, - String errorCode, Consumer requestConsumer) throws Exception { - - requestConsumer.accept(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Parameter: " + parameterName); - } - - private static MockHttpServletRequest createPushedAuthorizationRequest(RegisteredClient registeredClient) { - String requestUri = AuthorizationServerContextHolder.getContext() - .getAuthorizationServerSettings() - .getPushedAuthorizationRequestEndpoint(); - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - request.addParameter(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - request.addParameter(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - - return request; - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - @SuppressWarnings("unchecked") - private Map readPushedAuthorizationResponse(MockHttpServletResponse response) throws Exception { - final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { - }; - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return (Map) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java deleted file mode 100644 index 76e3b773f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -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.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.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2TokenEndpointFilter}. - * - * @author Madhu Bhat - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -public class OAuth2TokenEndpointFilterTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String REMOTE_ADDRESS = "remote-address"; - - private static final String ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"; - - private AuthenticationManager authenticationManager; - - private OAuth2TokenEndpointFilter filter; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - private final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2TokenEndpointFilter(this.authenticationManager); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenEndpointFilter(null, "tokenEndpointUri")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenTokenEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationDetailsSource cannot be null"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotTokenRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenRequestGetThenNotProcessed() throws Exception { - String requestUri = DEFAULT_TOKEN_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenRequestMissingGrantTypeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.removeParameter(OAuth2ParameterNames.GRANT_TYPE); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.GRANT_TYPE, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenRequestMultipleGrantTypeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.GRANT_TYPE, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenRequestInvalidGrantTypeThenUnsupportedGrantTypeError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.setParameter(OAuth2ParameterNames.GRANT_TYPE, "invalid-grant-type"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.GRANT_TYPE, - OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, request); - } - - @Test - public void doFilterWhenTokenRequestMissingCodeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.removeParameter(OAuth2ParameterNames.CODE); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.CODE, OAuth2ErrorCodes.INVALID_REQUEST, - request); - } - - @Test - public void doFilterWhenTokenRequestMultipleCodeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addParameter(OAuth2ParameterNames.CODE, "code-2"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.CODE, OAuth2ErrorCodes.INVALID_REQUEST, - request); - } - - @Test - public void doFilterWhenTokenRequestMultipleRedirectUriThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addParameter(OAuth2ParameterNames.REDIRECT_URI, "https://example2.com"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.REDIRECT_URI, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenRequestMultipleDPoPHeaderThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createAuthorizationCodeTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt-2"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2AccessToken.TokenType.DPOP.getValue(), - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenAuthorizationCodeTokenRequestThenAccessTokenResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(), - Instant.now().plus(Duration.ofDays(1))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken, refreshToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createAuthorizationCodeTokenRequest(registeredClient); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - ArgumentCaptor authorizationCodeAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationCodeAuthenticationToken.class); - verify(this.authenticationManager).authenticate(authorizationCodeAuthenticationCaptor.capture()); - - OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = authorizationCodeAuthenticationCaptor - .getValue(); - assertThat(authorizationCodeAuthentication.getCode()) - .isEqualTo(request.getParameter(OAuth2ParameterNames.CODE)); - assertThat(authorizationCodeAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(authorizationCodeAuthentication.getRedirectUri()) - .isEqualTo(request.getParameter(OAuth2ParameterNames.REDIRECT_URI)); - Map expectedAdditionalParameters = new HashMap<>(); - expectedAdditionalParameters.put("custom-param-1", "custom-value-1"); - expectedAdditionalParameters.put("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }); - expectedAdditionalParameters.put("dpop_proof", "dpop-proof-jwt"); - expectedAdditionalParameters.put("dpop_method", "POST"); - expectedAdditionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - assertThat(authorizationCodeAuthentication.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(expectedAdditionalParameters); - assertThat(authorizationCodeAuthentication.getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - - OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken(); - assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResult.getIssuedAt()).isBetween(accessToken.getIssuedAt().minusSeconds(1), - accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResult.getExpiresAt()).isBetween(accessToken.getExpiresAt().minusSeconds(1), - accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes()); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue()); - } - - @Test - public void doFilterWhenClientCredentialsTokenRequestMultipleScopeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createClientCredentialsTokenRequest( - TestRegisteredClients.registeredClient2().build()); - request.addParameter(OAuth2ParameterNames.SCOPE, "profile"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.SCOPE, OAuth2ErrorCodes.INVALID_REQUEST, - request); - } - - @Test - public void doFilterWhenClientCredentialsTokenRequestThenAccessTokenResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createClientCredentialsTokenRequest(registeredClient); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - ArgumentCaptor clientCredentialsAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2ClientCredentialsAuthenticationToken.class); - verify(this.authenticationManager).authenticate(clientCredentialsAuthenticationCaptor.capture()); - - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = clientCredentialsAuthenticationCaptor - .getValue(); - assertThat(clientCredentialsAuthentication.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(clientCredentialsAuthentication.getScopes()).isEqualTo(registeredClient.getScopes()); - Map expectedAdditionalParameters = new HashMap<>(); - expectedAdditionalParameters.put("custom-param-1", "custom-value-1"); - expectedAdditionalParameters.put("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }); - expectedAdditionalParameters.put("dpop_proof", "dpop-proof-jwt"); - expectedAdditionalParameters.put("dpop_method", "POST"); - expectedAdditionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - assertThat(clientCredentialsAuthentication.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(expectedAdditionalParameters); - assertThat(clientCredentialsAuthentication.getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - // For gh-281, check that expires_in is a number - assertThat(new ObjectMapper().readValue(response.getContentAsByteArray(), Map.class) - .get(OAuth2ParameterNames.EXPIRES_IN)).isInstanceOf(Number.class); - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - - OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken(); - assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResult.getIssuedAt()).isBetween(accessToken.getIssuedAt().minusSeconds(1), - accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResult.getExpiresAt()).isBetween(accessToken.getExpiresAt().minusSeconds(1), - accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes()); - } - - @Test - public void doFilterWhenRefreshTokenRequestMissingRefreshTokenThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createRefreshTokenTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.removeParameter(OAuth2ParameterNames.REFRESH_TOKEN); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.REFRESH_TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenRefreshTokenRequestMultipleRefreshTokenThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createRefreshTokenTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addParameter(OAuth2ParameterNames.REFRESH_TOKEN, "refresh-token-2"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.REFRESH_TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenRefreshTokenRequestMultipleScopeThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createRefreshTokenTokenRequest( - TestRegisteredClients.registeredClient().build()); - request.addParameter(OAuth2ParameterNames.SCOPE, "profile"); - - doFilterWhenTokenRequestInvalidParameterThenError(OAuth2ParameterNames.SCOPE, OAuth2ErrorCodes.INVALID_REQUEST, - request); - } - - @Test - public void doFilterWhenRefreshTokenRequestThenAccessTokenResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken, refreshToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createRefreshTokenTokenRequest(registeredClient); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - ArgumentCaptor refreshTokenAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2RefreshTokenAuthenticationToken.class); - verify(this.authenticationManager).authenticate(refreshTokenAuthenticationCaptor.capture()); - - OAuth2RefreshTokenAuthenticationToken refreshTokenAuthenticationToken = refreshTokenAuthenticationCaptor - .getValue(); - assertThat(refreshTokenAuthenticationToken.getRefreshToken()).isEqualTo(refreshToken.getTokenValue()); - assertThat(refreshTokenAuthenticationToken.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(refreshTokenAuthenticationToken.getScopes()).isEqualTo(registeredClient.getScopes()); - Map expectedAdditionalParameters = new HashMap<>(); - expectedAdditionalParameters.put("custom-param-1", "custom-value-1"); - expectedAdditionalParameters.put("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }); - expectedAdditionalParameters.put("dpop_proof", "dpop-proof-jwt"); - expectedAdditionalParameters.put("dpop_method", "POST"); - expectedAdditionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - assertThat(refreshTokenAuthenticationToken.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(expectedAdditionalParameters); - assertThat(refreshTokenAuthenticationToken.getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - - OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken(); - assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResult.getIssuedAt()).isBetween(accessToken.getIssuedAt().minusSeconds(1), - accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResult.getExpiresAt()).isBetween(accessToken.getExpiresAt().minusSeconds(1), - accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes()); - - OAuth2RefreshToken refreshTokenResult = accessTokenResponse.getRefreshToken(); - assertThat(refreshTokenResult.getTokenValue()).isEqualTo(refreshToken.getTokenValue()); - } - - @Test - public void doFilterWhenTokenExchangeRequestThenAccessTokenResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken, refreshToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenExchangeTokenRequest(registeredClient); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - ArgumentCaptor tokenExchangeAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2TokenExchangeAuthenticationToken.class); - verify(this.authenticationManager).authenticate(tokenExchangeAuthenticationCaptor.capture()); - - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthenticationToken = tokenExchangeAuthenticationCaptor - .getValue(); - assertThat(tokenExchangeAuthenticationToken.getSubjectToken()).isEqualTo("subject-token"); - assertThat(tokenExchangeAuthenticationToken.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE); - assertThat(tokenExchangeAuthenticationToken.getPrincipal()).isEqualTo(clientPrincipal); - assertThat(tokenExchangeAuthenticationToken.getScopes()).isEqualTo(registeredClient.getScopes()); - Map expectedAdditionalParameters = new HashMap<>(); - expectedAdditionalParameters.put("custom-param-1", "custom-value-1"); - expectedAdditionalParameters.put("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }); - expectedAdditionalParameters.put("dpop_proof", "dpop-proof-jwt"); - expectedAdditionalParameters.put("dpop_method", "POST"); - expectedAdditionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - assertThat(tokenExchangeAuthenticationToken.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(expectedAdditionalParameters); - assertThat(tokenExchangeAuthenticationToken.getDetails()) - .asInstanceOf(InstanceOfAssertFactories.type(WebAuthenticationDetails.class)) - .extracting(WebAuthenticationDetails::getRemoteAddress) - .isEqualTo(REMOTE_ADDRESS); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - - OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken(); - assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResult.getIssuedAt()).isBetween(accessToken.getIssuedAt().minusSeconds(1), - accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResult.getExpiresAt()).isBetween(accessToken.getExpiresAt().minusSeconds(1), - accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes()); - - OAuth2RefreshToken refreshTokenResult = accessTokenResponse.getRefreshToken(); - assertThat(refreshTokenResult.getTokenValue()).isEqualTo(refreshToken.getTokenValue()); - } - - @Test - public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - MockHttpServletRequest request = createAuthorizationCodeTokenRequest(registeredClient); - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request); - given(authenticationDetailsSource.buildDetails(any())).willReturn(webAuthenticationDetails); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationDetailsSource).buildDetails(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = new OAuth2AuthorizationCodeAuthenticationToken( - "code", clientPrincipal, null, null); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn(authorizationCodeAuthentication); - this.filter.setAuthenticationConverter(authenticationConverter); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createAuthorizationCodeTokenRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - - given(this.authenticationManager.authenticate(any())).willReturn(accessTokenAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createAuthorizationCodeTokenRequest(registeredClient); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - MockHttpServletRequest request = createAuthorizationCodeTokenRequest(registeredClient); - request.removeParameter(OAuth2ParameterNames.GRANT_TYPE); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - } - - private void doFilterWhenTokenRequestInvalidParameterThenError(String parameterName, String errorCode, - MockHttpServletRequest request) throws Exception { - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Parameter: " + parameterName); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static MockHttpServletRequest createAuthorizationCodeTokenRequest(RegisteredClient registeredClient) { - String[] redirectUris = registeredClient.getRedirectUris().toArray(new String[0]); - - String requestUri = DEFAULT_TOKEN_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.REDIRECT_URI, redirectUris[0]); - // The client does not need to send the client ID param, but we are resilient in - // case they do - request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - - return request; - } - - private static MockHttpServletRequest createClientCredentialsTokenRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_TOKEN_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); - request.addParameter(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - - return request; - } - - private static MockHttpServletRequest createRefreshTokenTokenRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_TOKEN_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); - request.addParameter(OAuth2ParameterNames.REFRESH_TOKEN, "refresh-token"); - request.addParameter(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - - return request; - } - - private static MockHttpServletRequest createTokenExchangeTokenRequest(RegisteredClient registeredClient) { - String requestUri = DEFAULT_TOKEN_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.setRemoteAddr(REMOTE_ADDRESS); - - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "subject-token"); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE); - request.addParameter(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java deleted file mode 100644 index 37fc4b868..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.HashSet; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2TokenIntrospectionEndpointFilter}. - * - * @author Gerardo Roza - * @author Joe Grandja - */ -public class OAuth2TokenIntrospectionEndpointFilterTests { - - private static final String DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI = "/oauth2/introspect"; - - private AuthenticationManager authenticationManager; - - private OAuth2TokenIntrospectionEndpointFilter filter; - - private final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2TokenIntrospectionEndpointFilter(this.authenticationManager); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenIntrospectionEndpointFilter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenTokenIntrospectionEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenIntrospectionEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenIntrospectionEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotTokenIntrospectionRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenIntrospectionRequestGetThenNotProcessed() throws Exception { - String requestUri = DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenIntrospectionRequestMissingTokenThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createTokenIntrospectionRequest("token", - OAuth2TokenType.ACCESS_TOKEN.getValue()); - request.removeParameter(OAuth2ParameterNames.TOKEN); - - doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenIntrospectionRequestMultipleTokenThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createTokenIntrospectionRequest("token", - OAuth2TokenType.ACCESS_TOKEN.getValue()); - request.addParameter(OAuth2ParameterNames.TOKEN, "other-token"); - - doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenIntrospectionRequestMultipleTokenTypeHintThenInvalidRequestError() throws Exception { - MockHttpServletRequest request = createTokenIntrospectionRequest("token", - OAuth2TokenType.ACCESS_TOKEN.getValue()); - request.addParameter(OAuth2ParameterNames.TOKEN_TYPE_HINT, OAuth2TokenType.ACCESS_TOKEN.getValue()); - - doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN_TYPE_HINT, - OAuth2ErrorCodes.INVALID_REQUEST, request); - } - - @Test - public void doFilterWhenTokenIntrospectionRequestValidThenSuccessResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - // @formatter:off - OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true) - .clientId("authorized-client-id") - .username("authorizing-username") - .issuedAt(accessToken.getIssuedAt()) - .expiresAt(accessToken.getExpiresAt()) - .scopes((scopes) -> scopes.addAll(accessToken.getScopes())) - .tokenType(accessToken.getTokenType().getValue()) - .notBefore(accessToken.getIssuedAt()) - .subject("authorizing-subject") - .audience("authorized-client-id") - .issuer("https://provider.com") - .id("jti") - .build(); - // @formatter:on - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, tokenClaims); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenIntrospectionAuthenticationResult); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenIntrospectionRequest(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN.getValue()); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - ArgumentCaptor tokenIntrospectionAuthentication = ArgumentCaptor - .forClass(OAuth2TokenIntrospectionAuthenticationToken.class); - - verifyNoInteractions(filterChain); - verify(this.authenticationManager).authenticate(tokenIntrospectionAuthentication.capture()); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(tokenIntrospectionAuthentication.getValue().getAdditionalParameters()).contains( - entry("custom-param-1", "custom-value-1"), - entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(response); - assertThat(tokenIntrospectionResponse.isActive()).isEqualTo(tokenClaims.isActive()); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(tokenClaims.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isEqualTo(tokenClaims.getUsername()); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(tokenClaims.getIssuedAt().minusSeconds(1), - tokenClaims.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(tokenClaims.getExpiresAt().minusSeconds(1), - tokenClaims.getExpiresAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(tokenClaims.getScopes()); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(tokenClaims.getTokenType()); - assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween(tokenClaims.getNotBefore().minusSeconds(1), - tokenClaims.getNotBefore().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(tokenClaims.getSubject()); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(tokenClaims.getAudience()); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(tokenClaims.getIssuer()); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId()); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), null); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenIntrospectionRequest(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN.getValue()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), null); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenIntrospectionRequest(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN.getValue()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - given(this.authenticationManager.authenticate(any())).willThrow(OAuth2AuthenticationException.class); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenIntrospectionRequest(accessToken.getTokenValue(), - OAuth2TokenType.ACCESS_TOKEN.getValue()); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - } - - private void doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(String parameterName, String errorCode, - MockHttpServletRequest request) throws Exception { - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Token Introspection Parameter: " + parameterName); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private OAuth2TokenIntrospection readTokenIntrospectionResponse(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse); - } - - private static MockHttpServletRequest createTokenIntrospectionRequest(String token, String tokenTypeHint) { - String requestUri = DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.TOKEN, token); - request.addParameter(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenTypeHint); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilterTests.java deleted file mode 100644 index c9d84ba97..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilterTests.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.HashSet; -import java.util.function.Consumer; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetails; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2TokenRevocationEndpointFilter}. - * - * @author Vivek Babu - * @author Joe Grandja - */ -public class OAuth2TokenRevocationEndpointFilterTests { - - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private AuthenticationManager authenticationManager; - - private OAuth2TokenRevocationEndpointFilter filter; - - private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - - @BeforeEach - public void setUp() { - this.authenticationManager = mock(AuthenticationManager.class); - this.filter = new OAuth2TokenRevocationEndpointFilter(this.authenticationManager); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationEndpointFilter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationManager cannot be null"); - } - - @Test - public void constructorWhenTokenRevocationEndpointUriNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2TokenRevocationEndpointFilter(this.authenticationManager, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("tokenRevocationEndpointUri cannot be empty"); - } - - @Test - public void setAuthenticationDetailsSourceWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationDetailsSource(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationDetailsSource cannot be null"); - } - - @Test - public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationConverter cannot be null"); - } - - @Test - public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationSuccessHandler cannot be null"); - } - - @Test - public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("authenticationFailureHandler cannot be null"); - } - - @Test - public void doFilterWhenNotTokenRevocationRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenRevocationRequestGetThenNotProcessed() throws Exception { - String requestUri = DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenTokenRevocationRequestMissingTokenThenInvalidRequestError() throws Exception { - doFilterWhenTokenRevocationRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request.removeParameter(OAuth2ParameterNames.TOKEN)); - } - - @Test - public void doFilterWhenTokenRevocationRequestMultipleTokenThenInvalidRequestError() throws Exception { - doFilterWhenTokenRevocationRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN, - OAuth2ErrorCodes.INVALID_REQUEST, - (request) -> request.addParameter(OAuth2ParameterNames.TOKEN, "token-2")); - } - - @Test - public void doFilterWhenTokenRevocationRequestMultipleTokenTypeHintThenInvalidRequestError() throws Exception { - doFilterWhenTokenRevocationRequestInvalidParameterThenError(OAuth2ParameterNames.TOKEN_TYPE_HINT, - OAuth2ErrorCodes.INVALID_REQUEST, (request) -> request - .addParameter(OAuth2ParameterNames.TOKEN_TYPE_HINT, OAuth2TokenType.ACCESS_TOKEN.getValue())); - } - - @Test - public void doFilterWhenTokenRevocationRequestValidThenSuccessResponse() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - accessToken, clientPrincipal); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenRevocationAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenRevocationRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - verify(this.authenticationManager).authenticate(any()); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - } - - @Test - public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - MockHttpServletRequest request = createTokenRevocationRequest(); - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request); - given(authenticationDetailsSource.buildDetails(any())).willReturn(webAuthenticationDetails); - this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - accessToken, clientPrincipal); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenRevocationAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationDetailsSource).buildDetails(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - accessToken, clientPrincipal); - - AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - given(authenticationConverter.convert(any())).willReturn(tokenRevocationAuthentication); - this.filter.setAuthenticationConverter(authenticationConverter); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenRevocationAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenRevocationRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationConverter).convert(any()); - } - - @Test - public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1)), - new HashSet<>(Arrays.asList("scope1", "scope2"))); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - accessToken, clientPrincipal); - - AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler); - - given(this.authenticationManager.authenticate(any())).willReturn(tokenRevocationAuthentication); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenRevocationRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - } - - @Test - public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - - AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - this.filter.setAuthenticationFailureHandler(authenticationFailureHandler); - - given(this.authenticationManager.authenticate(any())).willThrow(OAuth2AuthenticationException.class); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(clientPrincipal); - SecurityContextHolder.setContext(securityContext); - - MockHttpServletRequest request = createTokenRevocationRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - } - - private void doFilterWhenTokenRevocationRequestInvalidParameterThenError(String parameterName, String errorCode, - Consumer requestConsumer) throws Exception { - - MockHttpServletRequest request = createTokenRevocationRequest(); - requestConsumer.accept(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); - assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Token Revocation Parameter: " + parameterName); - } - - private OAuth2Error readError(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse); - } - - private static MockHttpServletRequest createTokenRevocationRequest() { - String requestUri = DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); - request.setServletPath(requestUri); - - request.addParameter(OAuth2ParameterNames.TOKEN, "token"); - request.addParameter(OAuth2ParameterNames.TOKEN_TYPE_HINT, OAuth2TokenType.ACCESS_TOKEN.getValue()); - - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java deleted file mode 100644 index 133ffd39b..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link ClientSecretBasicAuthenticationConverter}. - * - * @author Patryk Kostrzewa - * @author Joe Grandja - */ -public class ClientSecretBasicAuthenticationConverterTests { - - private ClientSecretBasicAuthenticationConverter converter = new ClientSecretBasicAuthenticationConverter(); - - @Test - public void convertWhenAuthorizationHeaderEmptyThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenAuthorizationHeaderNotBasicThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer token"); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenAuthorizationHeaderBasicWithMissingCredentialsThenThrowOAuth2AuthenticationException() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Basic "); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenAuthorizationHeaderBasicWithInvalidBase64ThenThrowOAuth2AuthenticationException() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Basic clientId:secret"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenAuthorizationHeaderBasicWithMissingSecretThenThrowOAuth2AuthenticationException() - throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "")); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenAuthorizationHeaderBasicWithValidCredentialsThenReturnClientAuthenticationToken() - throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "secret")); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("clientId"); - assertThat(authentication.getCredentials()).isEqualTo("secret"); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - } - - @Test - public void convertWhenConfidentialClientWithPkceParametersThenAdditionalParametersIncluded() throws Exception { - MockHttpServletRequest request = createPkceTokenRequest(); - request.addParameter("custom-param", "custom-value-1", "custom-value-2"); - request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "secret")); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("clientId"); - assertThat(authentication.getCredentials()).isEqualTo("secret"); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"), - entry("custom-param", new String[] { "custom-value-1", "custom-value-2" })); - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - private static MockHttpServletRequest createPkceTokenRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java deleted file mode 100644 index 95148b903..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link ClientSecretPostAuthenticationConverter}. - * - * @author Anoop Garlapati - */ -public class ClientSecretPostAuthenticationConverterTests { - - private final ClientSecretPostAuthenticationConverter converter = new ClientSecretPostAuthenticationConverter(); - - @Test - public void convertWhenMissingClientIdThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMultipleClientIdsThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenMissingClientSecretThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMultipleClientSecretsThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret-2"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenPostWithValidCredentialsThenReturnClientAuthenticationToken() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getCredentials()).isEqualTo("client-secret"); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); - } - - @Test - public void convertWhenConfidentialClientWithPkceParametersThenAdditionalParametersIncluded() { - MockHttpServletRequest request = createPkceTokenRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret"); - request.addParameter("custom-param", "custom-value-1", "custom-value-2"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getCredentials()).isEqualTo("client-secret"); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"), - entry("custom-param", new String[] { "custom-value-1", "custom-value-2" })); - } - - private static MockHttpServletRequest createPkceTokenRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverterTests.java deleted file mode 100644 index 7874be2cd..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverterTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link JwtClientAssertionAuthenticationConverter}. - * - * @author Rafal Lewczuk - */ -public class JwtClientAssertionAuthenticationConverterTests { - - private static final String JWT_BEARER_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - - private final JwtClientAssertionAuthenticationConverter converter = new JwtClientAssertionAuthenticationConverter(); - - @Test - public void convertWhenMissingClientAssertionTypeThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMissingClientAssertionThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMultipleClientAssertionTypeThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "other-client-assertion-type"); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - assertThrown(request, OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenNotJwtAssertionTypeThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "other-client-assertion-type"); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "other-assertion"); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMultipleClientAssertionThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "other-jwt-assertion"); - assertThrown(request, OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenMissingClientIdThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - assertThrown(request, OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenMultipleClientIdThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"); - assertThrown(request, OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenJwtAssertionThenReturnClientAuthenticationToken() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, JWT_BEARER_TYPE); - request.addParameter(OAuth2ParameterNames.CLIENT_ASSERTION, "jwt-assertion"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getCredentials()).isEqualTo("jwt-assertion"); - assertThat(authentication.getClientAuthenticationMethod().getValue()).isEqualTo(JWT_BEARER_TYPE); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry("custom-param-1", "custom-value-1"), - entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - } - - private void assertThrown(MockHttpServletRequest request, String errorCode) { - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(errorCode); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests.java deleted file mode 100644 index f30a9a8f5..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -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.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * Tests for {@link OAuth2AccessTokenResponseAuthenticationSuccessHandler}. - * - * @author Dmitriy Dubson - */ -public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests { - - private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - private final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private final OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( - this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, - this.registeredClient.getClientSecret()); - - private final OAuth2AccessTokenResponseAuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2AccessTokenResponseAuthenticationSuccessHandler(); - - @Test - public void setAccessTokenResponseCustomizerWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authenticationSuccessHandler.setAccessTokenResponseCustomizer(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("accessTokenResponseCustomizer cannot be null"); - // @formatter:on - } - - @Test - public void onAuthenticationSuccessWhenAuthenticationProvidedThenAccessTokenResponse() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient).build(); - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - Map additionalParameters = Collections.singletonMap("param1", "value1"); - Authentication authentication = new OAuth2AccessTokenAuthenticationToken(this.registeredClient, - this.clientPrincipal, accessToken, refreshToken, additionalParameters); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication); - - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResponse.getAccessToken().getIssuedAt()) - .isBetween(accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()) - .isBetween(accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResponse.getRefreshToken()).isNotNull(); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue()); - assertThat(accessTokenResponse.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(Map.of("param1", "value1")); - } - - @Test - public void onAuthenticationSuccessWhenInvalidAuthenticationTypeThenThrowOAuth2AuthenticationException() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - assertThatThrownBy(() -> this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, - new TestingAuthenticationToken(this.clientPrincipal, null))) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.SERVER_ERROR); - } - - @Test - public void onAuthenticationSuccessWhenAccessTokenResponseCustomizerSetThenAccessTokenResponseCustomized() - throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient).build(); - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - Map additionalParameters = Collections.singletonMap("param1", "value1"); - Authentication authentication = new OAuth2AccessTokenAuthenticationToken(this.registeredClient, - this.clientPrincipal, accessToken, refreshToken, additionalParameters); - - Consumer accessTokenResponseCustomizer = (authenticationContext) -> { - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = authenticationContext.getAuthentication(); - Map additionalParams = new HashMap<>(accessTokenAuthentication.getAdditionalParameters()); - additionalParams.put("authorization_id", authorization.getId()); - authenticationContext.getAccessTokenResponse().additionalParameters(additionalParams); - }; - this.authenticationSuccessHandler.setAccessTokenResponseCustomizer(accessTokenResponseCustomizer); - - this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication); - - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo(accessToken.getTokenValue()); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(accessToken.getTokenType()); - assertThat(accessTokenResponse.getAccessToken().getIssuedAt()) - .isBetween(accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1)); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()) - .isBetween(accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1)); - assertThat(accessTokenResponse.getRefreshToken()).isNotNull(); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue()); - assertThat(accessTokenResponse.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(Map.of("param1", "value1", "authorization_id", "id")); - } - - private OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return this.accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverterTests.java deleted file mode 100644 index c7ec48aee..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverterTests.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2DeviceAuthorizationConsentAuthenticationConverter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceAuthorizationConsentAuthenticationConverterTests { - - private static final String VERIFICATION_URI = "/oauth2/device_verification"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private static final String CLIENT_ID = "client-1"; - - private static final String STATE = "abc123"; - - private OAuth2DeviceAuthorizationConsentAuthenticationConverter converter; - - @BeforeEach - public void setUp() { - this.converter = new OAuth2DeviceAuthorizationConsentAuthenticationConverter(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void convertWhenGetThenReturnNull() { - MockHttpServletRequest request = createRequest(); - request.setMethod(HttpMethod.GET.name()); - assertThat(this.converter.convert(request)).isNull(); - } - - @Test - public void convertWhenMissingStateThenReturnNull() { - MockHttpServletRequest request = createRequest(); - assertThat(this.converter.convert(request)).isNull(); - } - - @Test - public void convertWhenMissingClientIdThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenEmptyClientIdThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, ""); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleClientIdParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "another"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.CLIENT_ID) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMissingUserCodeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenEmptyUserCodeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, ""); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenInvalidUserCodeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, "LONG-USER-CODE"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleUserCodeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter(OAuth2ParameterNames.USER_CODE, "another"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenEmptyStateParameterThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, ""); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.STATE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleStateParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.STATE, "another"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.STATE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMissingPrincipalThenReturnDeviceAuthorizationConsentAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getAuthorizationUri()).endsWith(VERIFICATION_URI); - assertThat(authentication.getClientId()).isEqualTo(CLIENT_ID); - assertThat(authentication.getPrincipal()).isInstanceOf(AnonymousAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getScopes()).isEmpty(); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - @Test - public void convertWhenMissingScopeThenReturnDeviceAuthorizationConsentAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getAuthorizationUri()).endsWith(VERIFICATION_URI); - assertThat(authentication.getClientId()).isEqualTo(CLIENT_ID); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getScopes()).isEmpty(); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - @Test - public void convertWhenAllParametersThenReturnDeviceAuthorizationConsentAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter(OAuth2ParameterNames.SCOPE, "message.read"); - request.addParameter(OAuth2ParameterNames.SCOPE, "message.write"); - request.addParameter("param-1", "value-1"); - request.addParameter("param-2", "value-1", "value-2"); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getAuthorizationUri()).endsWith(VERIFICATION_URI); - assertThat(authentication.getClientId()).isEqualTo(CLIENT_ID); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getScopes()).containsExactly("message.read", "message.write"); - assertThat(authentication.getAdditionalParameters()).containsExactly(Map.entry("param-1", "value-1"), - Map.entry("param-2", new String[] { "value-1", "value-2" })); - } - - @Test - public void convertWhenNonNormalizedUserCodeThenReturnDeviceAuthorizationConsentAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.STATE, STATE); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE.toLowerCase().replace("-", " . ")); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceAuthorizationConsentAuthenticationToken authentication = (OAuth2DeviceAuthorizationConsentAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getAuthorizationUri()).endsWith(VERIFICATION_URI); - assertThat(authentication.getClientId()).isEqualTo(CLIENT_ID); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getScopes()).isEmpty(); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.POST.name()); - request.setRequestURI(VERIFICATION_URI); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverterTests.java deleted file mode 100644 index 7296e35d1..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverterTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2DeviceAuthorizationRequestAuthenticationConverter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceAuthorizationRequestAuthenticationConverterTests { - - private static final String AUTHORIZATION_URI = "/oauth2/device_authorization"; - - private static final String CLIENT_ID = "client-1"; - - private OAuth2DeviceAuthorizationRequestAuthenticationConverter converter; - - @BeforeEach - public void setUp() { - this.converter = new OAuth2DeviceAuthorizationRequestAuthenticationConverter(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void convertWhenMultipleScopeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.SCOPE, "message.read"); - request.addParameter(OAuth2ParameterNames.SCOPE, "message.write"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SCOPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMissingScopeThenReturnDeviceAuthorizationRequestAuthenticationToken() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceAuthorizationRequestAuthenticationToken authentication = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getAuthorizationUri()).endsWith(AUTHORIZATION_URI); - assertThat(authentication.getScopes()).isEmpty(); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - @Test - public void convertWhenAllParametersThenReturnDeviceAuthorizationRequestAuthenticationToken() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.SCOPE, "message.read message.write"); - request.addParameter("param-1", "value-1"); - request.addParameter("param-2", "value-1", "value-2"); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceAuthorizationRequestAuthenticationToken authentication = (OAuth2DeviceAuthorizationRequestAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getAuthorizationUri()).endsWith(AUTHORIZATION_URI); - assertThat(authentication.getScopes()).containsExactly("message.read", "message.write"); - assertThat(authentication.getAdditionalParameters()).containsExactly(Map.entry("param-1", "value-1"), - Map.entry("param-2", new String[] { "value-1", "value-2" })); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.POST.name()); - request.setRequestURI(AUTHORIZATION_URI); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverterTests.java deleted file mode 100644 index 3ec7f458f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverterTests.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2DeviceCodeAuthenticationConverter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceCodeAuthenticationConverterTests { - - private static final String CLIENT_ID = "client-1"; - - private static final String TOKEN_URI = "/oauth2/token"; - - private static final String DEVICE_CODE = "EfYu_0jEL"; - - private OAuth2DeviceCodeAuthenticationConverter converter; - - @BeforeEach - public void setUp() { - this.converter = new OAuth2DeviceCodeAuthenticationConverter(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void convertWhenMissingGrantTypeThenReturnNull() { - MockHttpServletRequest request = createRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMissingDeviceCodeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.DEVICE_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleDeviceCodeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - request.addParameter(OAuth2ParameterNames.DEVICE_CODE, "another"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.DEVICE_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenAllParametersThenReturnDeviceCodeAuthenticationToken() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, CLIENT_ID); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - request.addParameter("param-1", "value-1"); - request.addParameter("param-2", "value-1", "value-2"); - request.addHeader(OAuth2AccessToken.TokenType.DPOP.getValue(), "dpop-proof-jwt"); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceCodeAuthenticationToken authentication = (OAuth2DeviceCodeAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getDeviceCode()).isEqualTo(DEVICE_CODE); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - Map expectedAdditionalParameters = new HashMap<>(); - expectedAdditionalParameters.put("param-1", "value-1"); - expectedAdditionalParameters.put("param-2", new String[] { "value-1", "value-2" }); - expectedAdditionalParameters.put("dpop_proof", "dpop-proof-jwt"); - expectedAdditionalParameters.put("dpop_method", "POST"); - expectedAdditionalParameters.put("dpop_target_uri", "http://localhost/oauth2/token"); - assertThat(authentication.getAdditionalParameters()) - .containsExactlyInAnyOrderEntriesOf(expectedAdditionalParameters); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.POST.name()); - request.setRequestURI(TOKEN_URI); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverterTests.java deleted file mode 100644 index 5ab5731fb..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverterTests.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2DeviceVerificationAuthenticationConverter}. - * - * @author Steve Riesenberg - */ -public class OAuth2DeviceVerificationAuthenticationConverterTests { - - private static final String VERIFICATION_URI = "/oauth2/device_verification"; - - private static final String USER_CODE = "BCDF-GHJK"; - - private OAuth2DeviceVerificationAuthenticationConverter converter; - - @BeforeEach - public void setUp() { - this.converter = new OAuth2DeviceVerificationAuthenticationConverter(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void convertWhenPutThenReturnNull() { - MockHttpServletRequest request = createRequest(); - request.setMethod(HttpMethod.PUT.name()); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenStateThenReturnNull() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.STATE, "abc123"); - updateQueryString(request); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMissingUserCodeThenReturnNull() { - MockHttpServletRequest request = createRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenEmptyUserCodeParameterThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, ""); - updateQueryString(request); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenInvalidUserCodeParameterThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, "LONG-USER-CODE"); - updateQueryString(request); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleUserCodeParameterThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter(OAuth2ParameterNames.USER_CODE, "another"); - updateQueryString(request); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.USER_CODE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMissingPrincipalThenReturnDeviceVerificationAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE.toLowerCase().replace("-", " . ")); - updateQueryString(request); - - OAuth2DeviceVerificationAuthenticationToken authentication = (OAuth2DeviceVerificationAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getPrincipal()).isInstanceOf(AnonymousAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - @Test - public void convertWhenNonNormalizedUserCodeThenReturnDeviceVerificationAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE.toLowerCase().replace("-", " . ")); - updateQueryString(request); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceVerificationAuthenticationToken authentication = (OAuth2DeviceVerificationAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getAdditionalParameters()).isEmpty(); - } - - @Test - public void convertWhenAllParametersThenReturnDeviceVerificationAuthentication() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.USER_CODE, USER_CODE); - request.addParameter("param-1", "value-1"); - request.addParameter("param-2", "value-1", "value-2"); - updateQueryString(request); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken("user", null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2DeviceVerificationAuthenticationToken authentication = (OAuth2DeviceVerificationAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getPrincipal()).isInstanceOf(TestingAuthenticationToken.class); - assertThat(authentication.getUserCode()).isEqualTo(USER_CODE); - assertThat(authentication.getAdditionalParameters()).containsExactly(Map.entry("param-1", "value-1"), - Map.entry("param-2", new String[] { "value-1", "value-2" })); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.GET.name()); - request.setRequestURI(VERIFICATION_URI); - return request; - } - - private static void updateQueryString(MockHttpServletRequest request) { - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(request.getRequestURI()); - request.getParameterMap().forEach((key, values) -> { - if (values.length > 0) { - for (String value : values) { - uriBuilder.queryParam(key, value); - } - } - }); - request.setQueryString(uriBuilder.build().getQuery()); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandlerTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandlerTests.java deleted file mode 100644 index 6b5863c7f..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandlerTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link OAuth2ErrorAuthenticationFailureHandler}. - * - * @author Dmitriy Dubson - */ -public class OAuth2ErrorAuthenticationFailureHandlerTests { - - private final OAuth2ErrorAuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler(); - - @Test - public void setErrorResponseConverterWhenNullThenThrowIllegalArgumentException() { - // @formatter:off - assertThatThrownBy(() -> this.authenticationFailureHandler.setErrorResponseConverter(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("errorResponseConverter cannot be null"); - // @formatter:on - } - - @Test - public void onAuthenticationFailureWhenValidExceptionThenErrorResponse() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "error description", "error uri"); - AuthenticationException authenticationException = new OAuth2AuthenticationException(error); - - this.authenticationFailureHandler.onAuthenticationFailure(request, response, authenticationException); - - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getContentAsString()).contains("invalid_request"); - assertThat(response.getContentAsString()).contains("error description"); - assertThat(response.getContentAsString()).contains("error uri"); - } - - @Test - public void onAuthenticationFailureWhenInvalidExceptionThenStatusResponse() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - AuthenticationException authenticationException = new BadCredentialsException("Not a valid exception."); - - HttpMessageConverter errorResponseConverter = mock(HttpMessageConverter.class); - this.authenticationFailureHandler.setErrorResponseConverter(errorResponseConverter); - - this.authenticationFailureHandler.onAuthenticationFailure(request, response, authenticationException); - - verifyNoInteractions(errorResponseConverter); - assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - assertThat(response.getContentAsString()).doesNotContain(OAuth2ParameterNames.ERROR); - assertThat(response.getContentAsString()).doesNotContain(OAuth2ParameterNames.ERROR_DESCRIPTION); - assertThat(response.getContentAsString()).doesNotContain(OAuth2ParameterNames.ERROR_URI); - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java deleted file mode 100644 index 05e211bb0..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link OAuth2TokenExchangeAuthenticationConverter}. - * - * @author Steve Riesenberg - */ -public class OAuth2TokenExchangeAuthenticationConverterTests { - - private static final String CLIENT_ID = "client-1"; - - private static final String TOKEN_URI = "/oauth2/token"; - - private static final String SUBJECT_TOKEN = "EfYu_0jEL"; - - private static final String ACTOR_TOKEN = "JlNE_xR1f"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private OAuth2TokenExchangeAuthenticationConverter converter; - - @BeforeEach - public void setUp() { - this.converter = new OAuth2TokenExchangeAuthenticationConverter(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void convertWhenMissingGrantTypeThenReturnNull() { - MockHttpServletRequest request = createRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenInvalidResourceThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.RESOURCE, "invalid"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.RESOURCE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenResourceContainsFragmentThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/#fragment"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.RESOURCE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleScopeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SCOPE, "one"); - request.addParameter(OAuth2ParameterNames.SCOPE, "two"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SCOPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleRequestedTokenTypeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenInvalidRequestedTokenTypeThenUnsupportedTokenTypeError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, "invalid"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); - // @formatter:on - } - - @Test - public void convertWhenMissingSubjectTokenThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleSubjectTokenParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "another"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMissingSubjectTokenTypeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenMultipleSubjectTokenTypeParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenInvalidSubjectTokenTypeThenUnsupportedTokenTypeError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, "invalid"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); - // @formatter:on - } - - @Test - public void convertWhenMultipleActorTokenParametersThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, "another"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenActorTokenAndMissingActorTokenTypeThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenActorTokenTypeAndMissingActorTokenThenInvalidRequestError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - // @formatter:on - } - - @Test - public void convertWhenInvalidActorTokenTypeThenUnsupportedTokenTypeError() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, "invalid"); - // @formatter:off - assertThatExceptionOfType(OAuth2AuthenticationException.class) - .isThrownBy(() -> this.converter.convert(request)) - .withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) - .extracting(OAuth2AuthenticationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); - // @formatter:on - } - - @Test - public void convertWhenAllParametersThenTokenExchangeAuthenticationToken() { - MockHttpServletRequest request = createRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource1"); - request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource2"); - request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience1"); - request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience2"); - request.addParameter(OAuth2ParameterNames.SCOPE, "one two"); - request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - - SecurityContextImpl securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); - SecurityContextHolder.setContext(securityContext); - - OAuth2TokenExchangeAuthenticationToken authentication = (OAuth2TokenExchangeAuthenticationToken) this.converter - .convert(request); - assertThat(authentication).isNotNull(); - assertThat(authentication.getResources()).containsExactly("https://mydomain.com/resource1", - "https://mydomain.com/resource2"); - assertThat(authentication.getAudiences()).containsExactly("audience1", "audience2"); - assertThat(authentication.getScopes()).containsExactly("one", "two"); - assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); - assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); - assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); - assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN); - assertThat(authentication.getActorTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); - } - - private static MockHttpServletRequest createRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(HttpMethod.POST.name()); - request.setRequestURI(TOKEN_URI); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverterTests.java deleted file mode 100644 index c522b3e68..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverterTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020-2023 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.server.authorization.web.authentication; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link PublicClientAuthenticationConverter}. - * - * @author Joe Grandja - */ -public class PublicClientAuthenticationConverterTests { - - private PublicClientAuthenticationConverter converter = new PublicClientAuthenticationConverter(); - - @Test - public void convertWhenNotPublicClientThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMissingClientIdThenInvalidRequestError() { - MockHttpServletRequest request = createPkceTokenRequest(); - request.removeParameter(OAuth2ParameterNames.CLIENT_ID); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenMultipleClientIdThenInvalidRequestError() { - MockHttpServletRequest request = createPkceTokenRequest(); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenMultipleCodeVerifierThenInvalidRequestError() { - MockHttpServletRequest request = createPkceTokenRequest(); - request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-2"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenPublicClientThenReturnClientAuthenticationToken() { - MockHttpServletRequest request = createPkceTokenRequest(); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"), - entry("custom-param-1", "custom-value-1"), - entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - } - - private static MockHttpServletRequest createPkceTokenRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"); - return request; - } - -} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverterTests.java deleted file mode 100644 index cc2600e54..000000000 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverterTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020-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.server.authorization.web.authentication; - -import java.security.cert.X509Certificate; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.entry; - -/** - * Tests for {@link X509ClientCertificateAuthenticationConverter}. - * - * @author Joe Grandja - */ -public class X509ClientCertificateAuthenticationConverterTests { - - private final X509ClientCertificateAuthenticationConverter converter = new X509ClientCertificateAuthenticationConverter(); - - @Test - public void convertWhenMissingX509CertificateThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenEmptyX509CertificateThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", new X509Certificate[0]); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMissingClientIdThenReturnNull() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - Authentication authentication = this.converter.convert(request); - assertThat(authentication).isNull(); - } - - @Test - public void convertWhenMultipleClientIdThenInvalidRequestError() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2"); - assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class) - .extracting((ex) -> ((OAuth2AuthenticationException) ex).getError()) - .extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); - } - - @Test - public void convertWhenPkiX509CertificateThenReturnClientAuthenticationToken() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", - TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry("custom-param-1", "custom-value-1"), - entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - } - - @Test - public void convertWhenSelfSignedX509CertificateThenReturnClientAuthenticationToken() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute("jakarta.servlet.request.X509Certificate", - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); - request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); - request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter("custom-param-1", "custom-value-1"); - request.addParameter("custom-param-2", "custom-value-1", "custom-value-2"); - OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter - .convert(request); - assertThat(authentication.getPrincipal()).isEqualTo("client-1"); - assertThat(authentication.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); - assertThat(authentication.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH); - assertThat(authentication.getAdditionalParameters()).containsOnly( - entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - entry(OAuth2ParameterNames.CODE, "code"), entry("custom-param-1", "custom-value-1"), - entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" })); - } - -} diff --git a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql deleted file mode 100644 index 5eb82db39..000000000 --- a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/client/custom-oauth2-registered-client-schema.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE oauth2RegisteredClient ( - id varchar(100) NOT NULL, - clientId varchar(100) NOT NULL, - clientIdIssuedAt timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, - clientSecret varchar(200) DEFAULT NULL, - clientSecretExpiresAt timestamp DEFAULT NULL, - clientName varchar(200) NOT NULL, - clientAuthenticationMethods varchar(1000) NOT NULL, - authorizationGrantTypes varchar(1000) NOT NULL, - redirectUris varchar(1000) DEFAULT NULL, - postLogoutRedirectUris varchar(1000) DEFAULT NULL, - scopes varchar(1000) NOT NULL, - clientSettings varchar(2000) NOT NULL, - tokenSettings varchar(2000) NOT NULL, - PRIMARY KEY (id) -); diff --git a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-consent-schema.sql b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-consent-schema.sql deleted file mode 100644 index 2c82b83a7..000000000 --- a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-consent-schema.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE oauth2AuthorizationConsent ( - registeredClientId varchar(100) NOT NULL, - principalName varchar(200) NOT NULL, - authorities varchar(1000) NOT NULL, - PRIMARY KEY (registeredClientId, principalName) -); diff --git a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql deleted file mode 100644 index c32430dce..000000000 --- a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE oauth2_authorization ( - id varchar(100) NOT NULL, - registered_client_id varchar(100) NOT NULL, - principal_name varchar(200) NOT NULL, - authorization_grant_type varchar(100) NOT NULL, - authorized_scopes varchar(1000) DEFAULT NULL, - attributes varchar(4000) DEFAULT NULL, - state varchar(500) DEFAULT NULL, - authorization_code_value clob DEFAULT NULL, - authorization_code_issued_at timestamp DEFAULT NULL, - authorization_code_expires_at timestamp DEFAULT NULL, - authorization_code_metadata varchar(2000) DEFAULT NULL, - access_token_value clob DEFAULT NULL, - access_token_issued_at timestamp DEFAULT NULL, - access_token_expires_at timestamp DEFAULT NULL, - access_token_metadata varchar(2000) DEFAULT NULL, - access_token_type varchar(100) DEFAULT NULL, - access_token_scopes varchar(1000) DEFAULT NULL, - oidc_id_token_value clob DEFAULT NULL, - oidc_id_token_issued_at timestamp DEFAULT NULL, - oidc_id_token_expires_at timestamp DEFAULT NULL, - oidc_id_token_metadata varchar(2000) DEFAULT NULL, - refresh_token_value clob DEFAULT NULL, - refresh_token_issued_at timestamp DEFAULT NULL, - refresh_token_expires_at timestamp DEFAULT NULL, - refresh_token_metadata varchar(2000) DEFAULT NULL, - user_code_value clob DEFAULT NULL, - user_code_issued_at timestamp DEFAULT NULL, - user_code_expires_at timestamp DEFAULT NULL, - user_code_metadata varchar(2000) DEFAULT NULL, - device_code_value clob DEFAULT NULL, - device_code_issued_at timestamp DEFAULT NULL, - device_code_expires_at timestamp DEFAULT NULL, - device_code_metadata varchar(2000) DEFAULT NULL, - PRIMARY KEY (id) -); diff --git a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql deleted file mode 100644 index 462240dbb..000000000 --- a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE oauth2Authorization ( - id varchar(100) NOT NULL, - registeredClientId varchar(100) NOT NULL, - principalName varchar(200) NOT NULL, - authorizationGrantType varchar(100) NOT NULL, - authorizedScopes varchar(1000) DEFAULT NULL, - attributes varchar(4000) DEFAULT NULL, - state varchar(500) DEFAULT NULL, - authorizationCodeValue varchar(1000) DEFAULT NULL, - authorizationCodeIssuedAt timestamp DEFAULT NULL, - authorizationCodeExpiresAt timestamp DEFAULT NULL, - authorizationCodeMetadata varchar(2000) DEFAULT NULL, - accessTokenValue varchar(1000) DEFAULT NULL, - accessTokenIssuedAt timestamp DEFAULT NULL, - accessTokenExpiresAt timestamp DEFAULT NULL, - accessTokenMetadata varchar(2000) DEFAULT NULL, - accessTokenType varchar(100) DEFAULT NULL, - accessTokenScopes varchar(1000) DEFAULT NULL, - oidcIdTokenValue varchar(1000) DEFAULT NULL, - oidcIdTokenIssuedAt timestamp DEFAULT NULL, - oidcIdTokenExpiresAt timestamp DEFAULT NULL, - oidcIdTokenMetadata varchar(2000) DEFAULT NULL, - refreshTokenValue varchar(1000) DEFAULT NULL, - refreshTokenIssuedAt timestamp DEFAULT NULL, - refreshTokenExpiresAt timestamp DEFAULT NULL, - refreshTokenMetadata varchar(2000) DEFAULT NULL, - userCodeValue varchar(1000) DEFAULT NULL, - userCodeIssuedAt timestamp DEFAULT NULL, - userCodeExpiresAt timestamp DEFAULT NULL, - userCodeMetadata varchar(2000) DEFAULT NULL, - deviceCodeValue varchar(1000) DEFAULT NULL, - deviceCodeIssuedAt timestamp DEFAULT NULL, - deviceCodeExpiresAt timestamp DEFAULT NULL, - deviceCodeMetadata varchar(2000) DEFAULT NULL, - PRIMARY KEY (id) -); diff --git a/samples/default-authorizationserver/samples-default-authorizationserver.gradle b/samples/default-authorizationserver/samples-default-authorizationserver.gradle index 4bd520b0a..b3581c0b5 100644 --- a/samples/default-authorizationserver/samples-default-authorizationserver.gradle +++ b/samples/default-authorizationserver/samples-default-authorizationserver.gradle @@ -21,7 +21,6 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.boot:spring-boot-starter-security" implementation "org.springframework.boot:spring-boot-starter-oauth2-authorization-server" - implementation project(":spring-security-oauth2-authorization-server") testRuntimeOnly "org.junit.platform:junit-platform-launcher" testImplementation "org.springframework.boot:spring-boot-starter-test" diff --git a/samples/demo-authorizationserver/gradle.properties b/samples/demo-authorizationserver/gradle.properties index 600028e8e..02c85c592 100644 --- a/samples/demo-authorizationserver/gradle.properties +++ b/samples/demo-authorizationserver/gradle.properties @@ -1 +1 @@ -spring-security.version=7.0.0-M2 +spring-security.version=7.1.0 diff --git a/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle b/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle index 218f6e656..fe9d07568 100644 --- a/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle +++ b/samples/demo-authorizationserver/samples-demo-authorizationserver.gradle @@ -1,6 +1,6 @@ plugins { - id "org.springframework.boot" version "4.0.0-M2" - id "io.spring.dependency-management" version "1.1.0" + id "org.springframework.boot" version "4.1.0" + id "io.spring.dependency-management" version "1.1.7" id "java" } @@ -23,11 +23,12 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-security" implementation "org.springframework.boot:spring-boot-starter-oauth2-client" implementation "org.springframework.boot:spring-boot-starter-jdbc" - implementation project(":spring-security-oauth2-authorization-server") + implementation "org.springframework.security:spring-security-oauth2-authorization-server" runtimeOnly "com.h2database:h2" testRuntimeOnly "org.junit.platform:junit-platform-launcher" testImplementation "org.springframework.boot:spring-boot-starter-test" + testImplementation "org.springframework.boot:spring-boot-webmvc-test" testImplementation "org.springframework.security:spring-security-test" testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.htmlunit:htmlunit" diff --git a/samples/demo-authorizationserver/src/main/java/sample/DemoAuthorizationServerApplication.java b/samples/demo-authorizationserver/src/main/java/sample/DemoAuthorizationServerApplication.java index 8f6e43c68..f6b33af35 100644 --- a/samples/demo-authorizationserver/src/main/java/sample/DemoAuthorizationServerApplication.java +++ b/samples/demo-authorizationserver/src/main/java/sample/DemoAuthorizationServerApplication.java @@ -47,7 +47,7 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { TypeReference.of(AuthorizationConsentController.ScopeWithDescription.class), TypeReference.of(Lists.class) ), builder -> - builder.withMembers(MemberCategory.DECLARED_FIELDS, + builder.withMembers(MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS) ); } diff --git a/samples/demo-authorizationserver/src/main/java/sample/authentication/DeviceClientAuthenticationProvider.java b/samples/demo-authorizationserver/src/main/java/sample/authentication/DeviceClientAuthenticationProvider.java index 2ba2668ee..8f0272fcb 100644 --- a/samples/demo-authorizationserver/src/main/java/sample/authentication/DeviceClientAuthenticationProvider.java +++ b/samples/demo-authorizationserver/src/main/java/sample/authentication/DeviceClientAuthenticationProvider.java @@ -15,8 +15,10 @@ */ package sample.authentication; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import sample.web.authentication.DeviceClientAuthenticationConverter; import org.springframework.security.authentication.AuthenticationProvider; @@ -27,9 +29,6 @@ import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; import org.springframework.util.Assert; /** @@ -38,11 +37,10 @@ * @since 1.1 * @see DeviceClientAuthenticationToken * @see DeviceClientAuthenticationConverter - * @see OAuth2ClientAuthenticationFilter */ public final class DeviceClientAuthenticationProvider implements AuthenticationProvider { private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1"; - private final Log logger = LogFactory.getLog(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); private final RegisteredClientRepository registeredClientRepository; public DeviceClientAuthenticationProvider(RegisteredClientRepository registeredClientRepository) { diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index 91f82e76c..9909f1f97 100644 --- a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -21,6 +21,8 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import sample.authentication.DeviceClientAuthenticationProvider; import sample.federation.FederatedIdentityIdTokenCustomizer; import sample.jose.Jwks; @@ -46,8 +48,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; @@ -57,8 +57,6 @@ import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer.authorizationServer; - /** * @author Joe Grandja * @author Daniel Garnier-Moiroux @@ -97,7 +95,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain( DeviceClientAuthenticationProvider deviceClientAuthenticationProvider = new DeviceClientAuthenticationProvider(registeredClientRepository); - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = authorizationServer(); + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); // @formatter:off http diff --git a/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerApplicationTests.java b/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerApplicationTests.java index 57acf38b9..6777d2f60 100644 --- a/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerApplicationTests.java +++ b/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerApplicationTests.java @@ -29,8 +29,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.HttpStatus; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.util.UriComponentsBuilder; @@ -46,22 +47,26 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"spring.profiles.include=test"}) @AutoConfigureMockMvc public class DemoAuthorizationServerApplicationTests { - private static final String REDIRECT_URI = "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc"; + private static final String REDIRECT_URI = "http://127.0.0.1:%d/login/oauth2/code/messaging-client-oidc"; - private static final String AUTHORIZATION_REQUEST = UriComponentsBuilder - .fromPath("/oauth2/authorize") - .queryParam("response_type", "code") - .queryParam("client_id", "messaging-client") - .queryParam("scope", "openid") - .queryParam("state", "some-state") - .queryParam("redirect_uri", REDIRECT_URI) - .toUriString(); + private String AUTHORIZATION_REQUEST; + + @LocalServerPort + private int port; @Autowired private WebClient webClient; @BeforeEach public void setUp() { + this.AUTHORIZATION_REQUEST = UriComponentsBuilder + .fromPath("/oauth2/authorize") + .queryParam("response_type", "code") + .queryParam("client_id", "messaging-client") + .queryParam("scope", "openid") + .queryParam("state", "some-state") + .queryParam("redirect_uri", String.format(REDIRECT_URI, port)) + .toUriString(); this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); this.webClient.getOptions().setRedirectEnabled(true); this.webClient.getCookieManager().clearCookies(); // log out diff --git a/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerConsentTests.java b/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerConsentTests.java index 10b7ae6f9..f9160a3ee 100644 --- a/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerConsentTests.java +++ b/samples/demo-authorizationserver/src/test/java/sample/DemoAuthorizationServerConsentTests.java @@ -29,8 +29,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.test.context.support.WithMockUser; @@ -55,22 +56,26 @@ public class DemoAuthorizationServerConsentTests { @Autowired private WebClient webClient; + @LocalServerPort + private int port; + @MockitoBean private OAuth2AuthorizationConsentService authorizationConsentService; - private final String redirectUri = "http://127.0.0.1/login/oauth2/code/messaging-client-oidc"; + private final String redirectUri = "http://127.0.0.1:%d/login/oauth2/code/messaging-client-oidc"; - private final String authorizationRequestUri = UriComponentsBuilder - .fromPath("/oauth2/authorize") - .queryParam("response_type", "code") - .queryParam("client_id", "messaging-client") - .queryParam("scope", "openid message.read message.write") - .queryParam("state", "state") - .queryParam("redirect_uri", this.redirectUri) - .toUriString(); + private String authorizationRequestUri; @BeforeEach public void setUp() { + authorizationRequestUri = UriComponentsBuilder + .fromPath("/oauth2/authorize") + .queryParam("response_type", "code") + .queryParam("client_id", "messaging-client") + .queryParam("scope", "openid message.read message.write") + .queryParam("state", "state") + .queryParam("redirect_uri", String.format(this.redirectUri, port)) + .toUriString(); this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); this.webClient.getOptions().setRedirectEnabled(true); this.webClient.getCookieManager().clearCookies(); diff --git a/settings.gradle b/settings.gradle index 6a29b040c..b58bf762f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,7 +8,7 @@ pluginManagement { } plugins { - id "io.spring.develocity.conventions" version "0.0.24" + id "io.spring.develocity.conventions" version "0.0.25" } dependencyResolutionManagement {